Why C# Doesn't Implement Indexed Properties

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.

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 my Indexer is not working?

After

StudentMemento s = new StudentMemento();

s.student will be null. The student field is only assigned within the indexer setter, so you need to call that before calling the getter e.g.

StudentMemento s = new StudentMemento();
s[1] = null;

Student s1 = s[1];
Student s2 = s[2];
Student s3 = s[1];

How do I find the generated indexer code in C#?

In C#, property getters accept no parameters, and setters accept exactly one parameter only, so it is rather impossible for the compiler to generate a C# property (in the C# sense) for the indexer. If you expected to find C# code declaring a property called Item, you won't find that.

What the compiler does generate, is IL, and this is what the documentation meant - "property" in the IL sense. IL has indexed properties, and that's what indexers translate to. For example, an indexer like this:

public class Foo
{
public string this[int index]
{
get => "";
set {}
}
}

translates to (play with this on SharpLab!):

.method public hidebysig specialname 
instance string get_Item (
int32 index
) cil managed
{
// Method begins at RVA 0x2050
// Code size 6 (0x6)
.maxstack 8

IL_0000: ldstr ""
IL_0005: ret
} // end of method Foo::get_Item

.method public hidebysig specialname
instance void set_Item (
int32 index,
string 'value'
) cil managed
{
// Method begins at RVA 0x2057
// Code size 2 (0x2)
.maxstack 8

IL_0000: nop
IL_0001: ret
} // end of method Foo::set_Item

// Properties
.property instance string Item(
int32 index
)
{
.get instance string Foo::get_Item(int32)
.set instance void Foo::set_Item(int32, string)
}

Notice that the Item property "takes a parameter". There is also a DefaultMemberAttribute added (not shown above).

Compare that last part with what normal C# properties translate to:

.property instance string Item()
{
.get instance string Foo::get_Item()
.set instance void Foo::set_Item(string)
}

Note that if you translate the IL code back to C#, you'll get the indexer back. Indexed properties don't exist 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.

Indexer named Item required by interface but not possible to implement?

C# can satisfy that .Item 'indexer' from the Interface with get_Item. This is because of how Property/Index getters and setters are generated during IL compilation.

Here is how it is described in the CLI Specification:

I.10.4 Naming patterns


For Properties:

An individual property is created by deciding on the type returned by its getter method and the
types of the getter’s parameters (if any). Then, two methods are created with names based on the
name of the property and these types.
For the examples below we define two properties: Name
takes no parameters and returns a System.String, while Item takes a System.Object parameter
and returns a System.Object. Item is referred to as an indexed property, meaning that it takes
parameters and thus can appear to the user as through it were an array with indices.

PropertyGet, used to read the value of the property
Pattern: <PropType> get_<PropName> (<Indices>)
Example: System.String get_Name ();
Example: System.Object get_Item (System.Object key);
PropertySet, used to modify the value of the property
Pattern: void set_<PropName> (<Indices>, <PropType>)
Example: void set_Name (System.String name);
Example: void set_Item (System.Object key, System.Object value);

Therefore you should be able to meet the conditions of the indexer implementing it with something like this:

public class ManagedEspritToolbar : Esprit.Toolbar
{
public ToolbarControl get_Item(int index) => Toolbar[index];
}

For testing this you can create a simple interface in VB.NET:

Public Interface IVBNetInterface
Property Item(index As Integer) As String
End Interface

Then implement the interface on a new class in C#. Note how it defaults to get_Item/set_Item accessors when allowing the IDE to auto-implement the interface:

public class CSharpClass : IVBNetInterface
{
public string get_Item(int index)
{
throw new NotImplementedException();
}

public void set_Item(int index, string Value)
{
throw new NotImplementedException();
}
}

Reading the generated IL of the Interface confirms this behavior:

Sample Image


What about VB.NET's Default Property?

In VB.NET, there is a Default property decorator which is essentially the mechanism for declaring an indexer on a class:

Public Interface IVBNetInterface
Default Property Item(index As Integer) As String
End Interface

When this is implemented correctly on the VB.NET class/interface, the standard C# this[int] indexing implementation will work. Therefore the get_Item workaround should only really be necessary when the Default attribute has not been properly applied to the target index property. Note the addition of the System.Reflection.DefaultMemberAttribute attribute when investigating the IL code once this has been applied:

Sample Image


Improving Usage:

To get around underlying classes/interfaces not being written with the Default modifier, you can implement the interface indexers explicitely, which allows exposing a traditional C# styled indexer on the class:

public class CSharpClass : IVBNetInterface
{
public string this[int index]
{
get => throw new NotImplementedException();
set => throw new NotImplementedException();
}

#region IVBNetInterface

string IVBNetInterface.get_Item(int index) => this[index];

void IVBNetInterface.set_Item(int index, string value) => this[index] = value;

#endregion
}

This may be the preferred approach if you want the usage of the class to be inferred through the typical indexer while still satisfying the underlying Interface.Item requirement.

Why C++ CLI indexed properties does not work in C#?

C++/CLI has indexed properties that will be available from C#:
Example:

public ref class ClassWithIndexer
{
private:
array<int> ^x;
public:
property int default[int]
{
int get(int index)
{
return x[index];
}
void set(int index, int value)
{
x[index] = value;
}
}
};

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]; }
}


Related Topics



Leave a reply



Submit