Easy Creation of Properties That Support Indexing in C#

Easy creation of properties that support indexing in C#

EDIT FOR 2022: This continues to get votes, but it probably isn't something I would use today primarily because it does push garbage collection in a way that would not be ideal at scale, if the property was being hit a lot. I remember this being a complicated topic, and I do not want to go deep on researching it right now, but I wonder if indexers could solve this problem today. See: https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/indexers/


I found your idea useful, so I extended it. This may not technically be a proper answer since I'm not sure it squarely answers your question, but I thought it might be useful to people who came here looking for property indexers.

First, I needed to be able to support get-only and set-only properties, so I made a slight variation of your code for these scenarios:

Get and Set (very minor changes):

public class IndexedProperty<TIndex, TValue>
{
readonly Action<TIndex, TValue> SetAction;
readonly Func<TIndex, TValue> GetFunc;

public IndexedProperty(Func<TIndex, TValue> getFunc, Action<TIndex, TValue> setAction)
{
this.GetFunc = getFunc;
this.SetAction = setAction;
}

public TValue this[TIndex i]
{
get
{
return GetFunc(i);
}
set
{
SetAction(i, value);
}
}
}

Get Only:

public class ReadOnlyIndexedProperty<TIndex, TValue>
{
readonly Func<TIndex, TValue> GetFunc;

public ReadOnlyIndexedProperty(Func<TIndex, TValue> getFunc)
{
this.GetFunc = getFunc;
}

public TValue this[TIndex i]
{
get
{
return GetFunc(i);
}
}
}

Set Only:

public class WriteOnlyIndexedProperty<TIndex, TValue>
{
readonly Action<TIndex, TValue> SetAction;

public WriteOnlyIndexedProperty(Action<TIndex, TValue> setAction)
{
this.SetAction = setAction;
}

public TValue this[TIndex i]
{
set
{
SetAction(i, value);
}
}
}

Example

Here's a simple usage example. I inherit from Collection and create a named indexer, as Jon Skeet called it. This example is intended to be simple, not practical:

public class ExampleCollection<T> : Collection<T>
{
public IndexedProperty<int, T> ExampleProperty
{
get
{
return new IndexedProperty<int, T>(GetIndex, SetIndex);
}
}

private T GetIndex(int index)
{
return this[index];
}
private void SetIndex(int index, T value)
{
this[index] = value;
}
}

ExampleCollection in the Wild

This hastily constructed unit test shows how it looks when you ExampleCollection in a project:

[TestClass]
public class IndexPropertyTests
{
[TestMethod]
public void IndexPropertyTest()
{
var MyExample = new ExampleCollection<string>();
MyExample.Add("a");
MyExample.Add("b");

Assert.IsTrue(MyExample.ExampleProperty[0] == "a");
Assert.IsTrue(MyExample.ExampleProperty[1] == "b");

MyExample.ExampleProperty[0] = "c";

Assert.IsTrue(MyExample.ExampleProperty[0] == "c");

}
}

Finally, if you want to use the get-only and set-only versions, that looks like this:

    public ReadOnlyIndexedProperty<int, T> ExampleProperty
{
get
{
return new ReadOnlyIndexedProperty<int, T>(GetIndex);
}
}

Or:

    public WriteOnlyIndexedProperty<int, T> ExampleProperty
{
get
{
return new WriteOnlyIndexedProperty<int, T>(SetIndex);
}
}

In both cases, the result works the way you would expect a get-only/set-only property to behave.

Named indexed property in C#?

The well-known solution is to create a proxy class:

public class MyClass
{
public class MyPropProxy
{
private MyClass c;

// ctor etc.

public string this[int index]
{
get
{
return c.list[index];
}
set
{
c.list[index] = value;
}
}
}

private List<string> list;
private MyPropProxy myPropProxy;

// ctor etc.

public MyPropProxy MyProp
{
get
{
return myPropProxy;
}
}
}

But (with exception, that this actually solves the problem), this solution introduces mostly only cons:

  • It causes the code to be polluted by (possibly) a lot of small proxy classes.
  • Presented solution breaks encapsulation a little (inner class accesses private members of the outer class), a better one would pass an instance of list to MyPropProxy's ctor, what would require even more code.
  • Exposing internal helper classes is not something I would suggest. One may solve that by introducing additional interface, but that's even one more entity to implement (test, maintain etc.)

There's another way, though. It also pollutes the code a little, but surely a lot less, than the previous one:

public interface IMyProp
{
string this[int index] { get; }
}

public class MyClass : IMyProp
{
private List<string> list;

string IMyProp.this[int index]
{
get
{
return list[index];
}
set
{
list[index] = value;
}
}

// ctor etc.

public IMyProp MyProp
{
get
{
return this;
}
}
}

Pros:

  • No proxy classes (which occupy space in memory, serve only a single purpose and (in the simplest solution) breaks encapsulation.
  • Simple solution, requires little code to add another indexed property

Cons:

  • Each property requires a different public interface
  • With increase of indexed properties, the class implements more and more interfaces

This is the simplest (in terms of code length and complexity) way of introducing indexed properties to C#. Unless someone posts even shorter and simpler one, of course.

Why C# doesn't implement indexed properties?

Here's how we designed C# 4.

First we made a list of every possible feature we could think of adding to the language.

Then we bucketed the features into "this is bad, we must never do it", "this is awesome, we have to do it", and "this is good but let's not do it this time".

Then we looked at how much budget we had to design, implement, test, document, ship and maintain the "gotta have" features and discovered that we were 100% over budget.

So we moved a bunch of stuff from the "gotta have" bucket to the "nice to have" bucket.

Indexed properties were never anywhere near the top of the "gotta have" list. They are very low on the "nice" list and flirting with the "bad idea" list.

Every minute we spend designing, implementing, testing, documenting or maintaining nice feature X is a minute we can't spend on awesome features A, B, C, D, E, F and G. We have to ruthlessly prioritize so that we only do the best possible features. Indexed properties would be nice, but nice isn't anywhere even close to good enough to actually get implemented.

Is named indexer property possible?

Depending on what you're really looking for, it might already be done for you. If you're trying to use an indexer on the Bars collection, it's already done for you::

Foo myFoo = new Foo();
Bar myBar = myFoo.Bars[1];

Or if you're trying to get the following functionality:

Foo myFoo = new Foo();
Bar myBar = myFoo[1];

Then:

public Bar this[int index]
{
get { return Bars[index]; }
}

Creating a COM indexed property from C#?

Indexed properties are available in .Net/C#, but you can't name them:

public String this[long index]
{
get; set;
}

This makes a property called Item but you don't use this name in C#:

myObj[1L];

If you want to call out to a named property in COM with C# 4.0, you can:

excel.Range["a"];

http://blogs.msdn.com/b/kirillosenkov/archive/2009/10/20/indexed-properties-in-c-4-0.aspx

Finally, if you want to have the index named for COM languages, you can use the IndexerNameAttribute to expose the indexer as a named property.

Access property in C# via indexer

A property cannot behave like an indexable item, unless it returns one. You have two options:

  • return an array, list or other item that you already have behind the scenes
  • create an object that provides an indexer and returns the values

The first one is simple, but it will allow changing the array/list/whatever. The second one can have only a getter, so it can be made read only.

I'm pretty sure it wouldn't be many lines of code to make a generic template with indexer and getter to encapsulate the actual object storing the values if it is needed.

C# Multiple Indexers

Not in C#, no.

However, you can always return collections from properties, as follows:

public IList<Foo> Foos
{
get { return ...; }
}

public IList<Bar> Bars
{
get { return ...; }
}

IList<T> has an indexer, so you can write the following:

C whatever = new C();
Foo myFoo = whatever.Foos[13];

On the lines "return ...;" you can return whatever implements IList<T>, but you might what to return a read-only wrapper around your collection, see AsReadOnly() method.



Related Topics



Leave a reply



Submit