Support Named Indexers

Topics: C# Language Design
Apr 17, 2014 at 8:32 AM
Very often we need to use object like collection for this purposes we have Indexers:
public    T     this[Int32 index]{...}
that internally looks like:
public    T     _Items [Int32 index]{...}
also we need different indexation on the same object:
SomeObject.XXX[4] = ...
SomeObject.YYY[8] = ...
SomeObject.ZZZ["fff"] = ...
but we do not want to create XXX, YYY, ZZZ collections or Interfaces, we want named Indexers
class SomeClass
{
   public    Int32     XXX [Int32 index]{...}
   public    String    YYY [Int32 index]{...}
   public    Object   ZZZ [String index]{...}
}
IL always support that and I can write named Indexers on C++ CLI but C# can not use them.

Also will be good if we can iterate through field of the same type:
class Test
{
   public   Int32    Coef_0;
   public   Int32    Coef_1;
   public   Int32    Coef_2;
   public   Int32    Coef_3;

   public   Int32    Coef[Int32 index]{ Coef_0..Coef_3 };
}

...

Test testObj = new Test();

for( var i = 0; i < 4; ++i )
   testObj.Coeff[i] = GetCoeff(...);
Apr 17, 2014 at 11:51 AM
It can be done with a small helper struct and lambdas:
public struct Helper<TKey, TValue>
{
    private readonly Func<TKey, TValue> _getter;
    private readonly Action<TKey, TValue> _setter;

    public Helper(Func<TKey, TValue> getter, Action<TKey, TValue> setter)
    {
        _getter = getter;
        _setter = setter;
    }

    public TValue this[TKey key]
    {
        get { return _getter(key); }
        set { _setter(key, value); }
    }
}

public class Foo
{
    Helper<int, int> NamedIndexer
    {
        get
        {
            return new Helper<int, int>(key =>
            {
                //Getter
                return 0;
            },
            (key, value) =>
            {
                //Setter
            });
        }
    }
}
Apr 17, 2014 at 1:06 PM
Yes but this solution creates 3 new Heap object on every access to NamedIndexer field of foo and thus very garbage collector heavy.
CLR already have Named Indexers and even more Indexers always Named and Name of C# Indexer always _Items.

Allow C# to set Indexer name explicitly must be very easy
Apr 17, 2014 at 4:07 PM
JesInc28 wrote:
Yes but this solution creates 3 new Heap object on every access to NamedIndexer field of foo and thus very garbage collector heavy.
Actually it creates only 2 heap objects: structs are value types, so they aren't heap objects.

And the two heap objects for the delegates could be avoided by caching them in fields, but then the code becomes quite verbose.
Apr 17, 2014 at 9:40 PM
Just to note, parameterized (or indexed) properties is something that VB.NET has had since version 1.0:
Public Property XXX(ByVal index As Integer) As Integer
    Get
        Return 0
    End Get
    Set(ByVal value As Integer)
        ' Do Nothing
    End Set
End Property
This creates property accessor methods that have the index parameters:
.property instance int32 XXX
{
    .get instance int32 Project1.Program::get_XXX(int32)
    .set instance void Project1.Program::set_XXX(int32, int32)
}
Since the C# compiler doesn't understand them you'd have to call them by the accessor methods directly:
int x = o.get_XXX(0);
o.set_XXX(x, 1);
Not arguing for or against, just noting that it's not without precedent and could be considered a matter of feature parity.
Apr 19, 2014 at 5:46 PM
I would also like this... it isn't often I need it now, but when I do it would make the code much easier to read.
Apr 21, 2014 at 1:36 PM
I'd like to have this feature in C# too. That would go towards language parity with VB and would expose an already existing feature of the CLR.

The syntax probably doesn't require too much thinking: just replace this with an actual identifier, and you're done. I also suspect it would be pretty simple to implement, since most of the code is already there for unnamed indexers.
Apr 21, 2014 at 7:14 PM
This sounds like a good idea to me, though depending how hard it is, I wouldn't necessarily prioritize it over other features. Maybe add an issue to the tracker so people can vote on it?
Apr 22, 2014 at 11:07 PM
I also sometimes had situations, where I was sad, that C# has no named indexes. But I'm not sure how this feature will live in C#. Currently in C# indexer access is done with [] operator. So lets we have such class
class SomeClass
{
   public    int     this[int   index]{...}
   public    int     XXX [int   index]{...}
   public    int     SomeMethod()     {...}
   public    int     SomeProp         {...}
}
In that case, we have:
var a = new SomeClass();
var b = a;                            //Here we access a with type SomeClass
var c = a[0];                         //Here we access default indexer with int result type
var d = a.SomeProp;                   //Here we access property with int result type
var e = a.SomeMethod;                 //It is illegal in c#. a.SomeMethod is "method group", but next two is legal
var e1 = new Func<int>(a.SomeMethod); //Here we access method delagate
var e2 = (Func<int>)(a.SomeMethod);   //Here we access method delagate
var f = a.XXX;             //It will be also illegal, as it is not some special construct, but rather part of indexer accessor.
So earlier every symbol name was legal even without additional operator, but with this change will be introduced some kind of "not full accessor". It will be always illegal to use a.XXX without a.XXX[...],
Apr 23, 2014 at 10:41 AM
a.XXX acually 2 funations:
Int32 get_XXX( Int32 index )
set_XXX( Int32 index, Int32 value )
var e = a.SomeMethod;                 //It is illegal in c#. a.SomeMethod is "method 
var e1 = new Func<int>(a.SomeMethod); //Here we access method delagate
var e2 = (Func<int>)(a.SomeMethod);   //Here we access method delagate

var f = a.XXX;             //It will be also illegal, as it is not some special 

//I think it should be:

var iname = nameof(a.XXX);             //legal
var iget = (Func<int, int>)a.XXX; //Here we access get method delagate
var iset = (Action<int, int>)a.XXX;   //Here we access set method delagate
Apr 23, 2014 at 2:24 PM
@JesInc28:

I don't think that converting an indexer is very necessary. Normal properties do not support the same thing, for example, if Prop was a property, these two are not valid:
var get = (Func<int>)a.Prop;
var set = (Action<int>)a.Prop;
So why would they be valid with indexers? I view it as a sort of "all or nothing", doesn't really make sense to have it for indexers but not properties.

Of course, if the user really does wants to create delegates, they could do the following for properties, and also indexers:
Func<int> get = () => a.Prop;
Action<int> set = v => a.Prop = v;
// and for indexed properties:
Func<int, int> get = i => a.IProp[i];
Action<int, int> set = (i, v) => a.IProp[i] = v;
It's a bit noisy and hard to understand, yes, (though a lot of the noise is coming from the type) but I feel like it's better to keep consistency between an indexed property and a normal property, rather than magically allow getting a delegate from an indexed property and leaving normal properties alone.

So I agree with @darkman666 with the fact that a.XXX should always be invalid.
Apr 23, 2014 at 2:43 PM
Edited Apr 23, 2014 at 2:44 PM
@khyperia:

Actually I agree. There is no good idea.
Bug getting delegate to a real property get/set method through PropertyGroup will be good:
var ddd = new Action( a.SomeMethod );

var get = new Func<int>( a.Prop );
var set = new Action<int>( a.Prop );

var iget = new Func<int, int>( a.Indexer );
var iset = new Action<int, int>( a.Indexer );
Today we can get access to these methods only via Reflection.
Developer
Apr 23, 2014 at 11:08 PM
Edited Apr 23, 2014 at 11:09 PM
Please explain why creating nested classes with separate indexers does not satisfy your requirement.

I don't believe this is a feature you're using very often, so extra typing isn't a very good reason why we should add a whole new feature.

If you are using it a lot, I'd like an actual example, because I believe multiple indexers is rarely appropriate.
May 2, 2014 at 12:06 AM
JesInc28 wrote:
Yes but this solution creates 3 new Heap object on every access to NamedIndexer field of foo and thus very garbage collector heavy.
If C# would add an attribute via which structures could say which methods or properties are safe on read-only instances, a struct which contained nothing but a reference to the container and chained this[] to a couple of its methods could be used as an indexer. Unfortunately, there's no way to tell the compiler to allow use of such a structure's indexed property setter in useful scenarios.