24

What C# interface should be used if I only want to be able to index into instances of a type? I don't need (or want) the ability to add/remove/edit elements. Enumeration is okay. Does this require a custom IIndexable type?

In this case IList is overkill because it forces implementation of members I don't want to have.

Brian Triplett
  • 3,462
  • 6
  • 35
  • 61

5 Answers5

38

As of .Net 4.5 the (arguably) right way is now to use IReadOnlyList<T>, which derives from IReadOnlyCollection<T>, which derives from IEnumerable<T>

  • IReadOnlyList<T> gives you the indexer
  • IReadOnlyCollection<T> gives you the collection Count property.
  • IEnumerable<T> gives you the collection Enumerator.

Its about as light as you can make it.

Core classes that directly implement IReadOnlyList:

Community
  • 1
  • 1
Meirion Hughes
  • 24,994
  • 12
  • 71
  • 122
  • 2
    +1 That's the best approach for .NET 4.5+. Generic collections (`List`) and arrays (`T[]`) all implement `IReadOnlyList`, so they can be passed around transparently, effectively making them immutable to consumers. I implemented my own version of [`IIndexable`](http://www.codeproject.com/Articles/233738/IIndexable-a-true-read-only-list-interface) several years ago, which is now obsolete. – vgru Jun 27 '15 at 12:51
14

IList<> (assuming you want to stay generic) is the only interface to include an indexer.

However, you can just explicitly implement and throw NotSupportedException for all those operations you don't want to support, or just implement IEnumerable<> and have the rest on the class only, not in an interface.

Lucero
  • 59,176
  • 9
  • 122
  • 152
3

You can expose it as IEnumerable<T> and still have indexing behavior via LINQ .ElementAt and .Count. If the underlying collection supports IList<T>, these are still O(1) operations as LINQ is smart enough to optimize for list implementations. Granted, .ElementAt is not as succinct as an indexer, but it also gives you flexibility if the underlying implementation changes to a collection that doesn't directly support indexing. If you don't want to use this approach, it's not too difficult to create a wrapper class that implements a read-only indexer and delegates all calls to an underlying list.

Dan Bryant
  • 27,329
  • 4
  • 56
  • 102
  • I know this post is old, but this answer is missleading. I'm sorry, but this has nothing to do with LINQ optimizing anything or being "smart". The only reason element indexing complexity is O(1) for IEnumerable's ElementAt() comes from the underlying type's implementation of it. No one should index an IEnumerable via ElementAt method based on an assumption, that i's PROBABLY implemented as a List. You see an IEnumerable, you assume it's IEnumerable and nothing more. You want an indexable interface then utilize an interface meant for actual indexer access. But you probably know that by now:) – Tarec Dec 12 '19 at 15:32
  • 1
    @Tarec, Nowadays, it's a moot point, since there are better options in the read-only interfaces, but I understand where you're coming from. At the time, it was considered best practice to expose read-only collections as `IEnumerable` with the understanding that in most circumstances, this would be reasonably performant to access via `ElementAt`. There wasn't an explicit contract to this effect in the language, so you had to either assume this or rely on documentation to clarify that this was, indeed, what you'd get. That said, the modern approach is superior and this answer is outdated. – Dan Bryant Dec 12 '19 at 16:45
1

Why not just IEnumerable<> + a custom interface with just an indexer?

Mau
  • 14,234
  • 2
  • 31
  • 52
0

Define a custom class for it that you can use in your interface

public class Indexable<T>
{
    private readonly IList<T> _innerList;

    public Indexable(IList<T> innerList)
    {
        _innerList = innerList;
    }

    public T this[int index]
    {
        get { return _innerList[index]; }
        set { _innerList[index] = value; }
    }
}

Use it as

public interface MyStuff
{
    Indexable<int> MyIndexableCollectionOfInt { get; }
}
Stijn Van Antwerpen
  • 1,840
  • 17
  • 42