Share via


What are named indexers?

Someone asked me about this, so I decided to write up the answer here in case other folks are interested.

Indexers are pretty well documented on the web; I’ll quote some here.

https://www.csharphelp.com/archives/archive140.html

C# introduces a new concept known as Indexers which are used for treating an object as an array. The indexers are usually known as smart arrays in C# community. Defining a C# indexer is much like defining properties. We can say that an indexer is a member that enables an object to be indexed in the same way as an array.

  <modifier> <return type> this [argument list]
  {
   get
   {
    // Get codes goes here
   }
   set
   {
    // Set codes goes here
   }
  }

I don’t usually know them as “smart arrays” which must mean I’m not really part of the C# community. But I’m trying!

Or the official source: https://msdn.microsoft.com/library/en-us/csref/html/vclrfindexedpropertiespg.asp

Indexers allow you to index a class or a struct instance in the same way as an array.

Hmm, that’s better. Let’s have an example:

class MyCollection<T>

{

      T this[uint i]

      {

            get

            {

                  return //...

            }

      }

}

class C

{

      static void Main(string[] args)

      {

            MyCollection<string> someStrings = ...;

            // here we index into the object like we would into an array

            Console.WriteLine(someStrings[1]);

      }

}

On the language tools side, indexers are a little funky. They look a lot like methods (parameter list, return type, accessibility) and a lot like properties (separate ‘get’ and ‘set’ accessors) and a bit different from both (no name).

So, what are named indexers?

 

Well, regular indexers don’t have a name, you just use them. But what if there are two different ways to index in to your object? You could overload, if that makes sense for your domain:

      T this[uint i] { get { /*...*/ } }

      T this[string s] { get { /*...*/ } }

Or you could use my “named indexer pattern” to create the appearance of a named indexer. Here’s an example with 2 named indexers to give some context.

class Car

{

      //

      // wheels

      //

      object[] wheels;

      public IIndexer<object, uint>.Get Wheels { get { return new WheelsHelper(this); } }

      public struct WheelsHelper : IIndexer<object, uint>.Get

      {

            readonly Car _outer;

            public WheelsHelper(Car mc) { this._outer = mc; }

            object IIndexer<object, uint>.Get.this[uint index] { get { return this._outer.wheels[index]; } }

      }

      //

      // seats

      //

      object[] seats;

      public SeatsHelper Seats { get { return new SeatsHelper(this); } }

      public struct SeatsHelper

      {

            readonly Car _outer;

            public SeatsHelper(Car mc) { this._outer = mc; }

            public object this[int index] { get { return this._outer.seats[index]; } }

      }

}

 

Now you can just write ‘car.Seats[1]’ and it works.

BTW, to compile this code, you’ll also need this code, which I just made up. It’s an experiment; not sure if I like it or not.

static class IIndexer<T, I>

{

      public interface Get { T this[I index] { get;} }

      public interface Set { T this[I index] { set;} }

      public interface Both : Get, Set { }

}

Comments

  • Anonymous
    July 22, 2004
    public ReadOnlyCollection<object> Wheels { get { return new ReadOnlyCollection<object>(wheels); } }

  • Anonymous
    July 22, 2004
    See my modified example on http://24.odessa.ua/CSharp/Named_Indexer.html

    An questions:
    a) Will WheelsHelper struct boxed to represent interface IIndexer<object, uint>.Get ?
    b) Visibility for all helper classes members
    c) How about using delegates for named indexers ?

    ///
    /// doors
    ///
    object[] doors = new object[0];
    /// Init to new DelegateIndexer<object, uint> (get_Door) inside constructor
    public readonly DelegateIndexer<object, uint> Doors;
    /// It's nice to keep code for accessor inside class
    /// No needs to use this._outer
    private object get_Door(uint index)
    {
    return doors[index];
    }

    public sealed class DelegateIndexer<T, I>
    {
    public delegate T Getter(I index);
    private readonly Getter getter;
    public DelegateIndexer(Getter getter)
    {
    this.getter = getter;
    }
    public T this[I index] { get { return this.getter(index); } }
    }

    Thanks ;-)

  • Anonymous
    July 23, 2004
    I'm not a big fan of named indexers. Sure, you can have car.Wheels[0] and car.Doors[0], but then you hit a brick wall when you want car.Wheels.Count, or better yet foreach(Wheel wheel in car.Wheels). Named indexers provide what is usually only one of the important aspects of a collection, indexed access. Back in my VB6 days, I would sometimes start out using more than one property with parameters (an indexer) on a class, but then end up changing my code because I would invariably end up needing either a count or a For Each enumeration on that member. I could then either add WheelsCount and DoorsCount properties directly to the Car class, but that's really an ugly workaround.

    I'm not saying there's never a need for a named indexer or property with parameters, and I can certainly think of a few cases where it may be nice, but it's not like you can't do the same thing with an additional class. Plus, using an additional class lets you add functionality later.

  • Anonymous
    July 23, 2004
    Matthew: All provided examples can be extended to provide .Count and .GetEnumerator() in addition to this[index] method.

    However, I agree with you that additional entity to represent WheelsCollection will be nice, intuitive and OO solution.

  • Anonymous
    July 24, 2004
    The biggest issure is that a VB.NET programmer could write Car.Wheels(0) with a named indexer and NEVER be able to add .Count and .GetEnumerator() without breaking compatibility. Of course I hope that a library programmer wouldn't use a named indexer in the first place due to CLS-compliance (not that any solution involving generics would work, either...unfortunately).

    The fact that you could add .Count and .GetEnumerator() is one of the reasons that some sort of helper class is needed. Your solution is already better than a named indexer. I don't know what the specific reasons the C# designers didn't allow more than one named indexer are, but I agree with the decision nonetheless.

    Anyway, I've been thinking about this, and wouldn't this solution work? What I like is that you automatically get an up-to-date type-safe wrapper around the internal array that implements IList<T>.

    class Car {
    private Wheel[] wheels;
    private Door[] doors;
    private IList<Wheel> wheelsList;
    private IList<Door> doorsList;

    public Car() {
    wheels = new Wheel[4] { ... };
    doors = new Door[4] { ... };
    wheelsList = Array.AsReadOnly<Wheel>(wheels);
    doorsList = Array.AsReadOnly<Door>(doors);
    }

    public IList<Wheel> Wheels {
    get { return wheelsList; }
    }

    public IList<Door> Doors {
    get { return doorsList; }
    }

    }

  • Anonymous
    January 22, 2009
    PingBack from http://www.hilpers.pl/214367-d-ugie-vc2005-c-kilka

  • Anonymous
    June 18, 2009
    PingBack from http://wheelbarrowstyle.info/story.php?id=2356

  • Anonymous
    June 18, 2009
    PingBack from http://adirondackchairshub.info/story.php?id=3206