5

A few languages - like Delphi - has a very convenient way of creating indexers: not only the whole class, but even single properties can be indexed, for instance:

type TMyClass = class(TObject)
protected
    function GetMyProp(index : integer) : string;
    procedure SetMyProp(index : integer; value : string);
public
    property MyProp[index : integer] : string read GetMyProp write SetMyProp;
end;

This can be used easily:

var c : TMyClass;

begin
    c = TMyClass.Create;
    c.MyProp[5] := 'Ala ma kota';
    c.Free;
end;

Is there a way to achieve the same effect in C# easily?

Spook
  • 25,318
  • 18
  • 90
  • 167
  • possible duplicate of [Is following Indexer property possible?](http://stackoverflow.com/questions/3293495/is-following-indexer-property-possible) – nawfal Jun 05 '14 at 21:04
  • @nawfal I see no interface in the solution? (this is knowledge-sharing question, BTW - the answer is mine as well) – Spook Jun 07 '14 at 14:36
  • The q is exactly the same, OP is after named indexers. You could provide an answer in that thread. You may not have seen the original q at the time of answering, but had you, then adding another question for merely answering is not appropriate. Btw, good answer, +1 (in some related q, this answer is also covered). – nawfal Jun 07 '14 at 15:27
  • @nawfal I believe, that there's a special checkbox when you ask a question named "Answer your question - share knowledge in Q&A style". – Spook Jun 07 '14 at 17:01
  • yes, but that's reserved for non-duplicate questions. My problem is not with your answer ("knowledge"), but the question. No big deal. Duplicates happen all the time. Its up to us as responsible members to do the clean up. Just doing my part. – nawfal Jun 07 '14 at 17:02
  • Possible duplicate of [Easy creation of properties that support indexing in C#](https://stackoverflow.com/questions/3344620/easy-creation-of-properties-that-support-indexing-in-c-sharp) – StayOnTarget Nov 06 '19 at 18:21

2 Answers2

14

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.

Spook
  • 25,318
  • 18
  • 90
  • 167
  • explicitly implemented interfaces, hadn't thought of that, nice! – George Birbilis May 30 '16 at 03:07
  • 1
    This is pretty clean.. but it could be made even cleaner by using templates: `public interface IIndexedProp { ValueT this[IndexT index] { get; } }` usage: `public class MyClass: IIndexedProp` And the rest of the class is the same. This way you only ever need 1 interface - maybe more for get/set only props. – H B Aug 28 '18 at 09:35
0

This is based on H B's comment, but expanded a little and as a code block (making copying easier):

public interface  IIndexedProperty<TValue> : IIndexedProperty<int, TValue> {}
public interface IReadOnlyIndexedProperty<out TValue> : IReadOnlyIndexedProperty<int, TValue> {}

public interface IIndexedProperty<in TIndex, TValue>
{
    TValue this[TIndex index] { get; set; }
}

public interface IReadOnlyIndexedProperty<in TIndex, out TValue>
{
    TValue this[TIndex index] { get; }
}

This uses covarients from C# 9.0, so if you are using an older version, strip out the in and out statements.

Pros: Most common indexed properties use a simple int index, so this allows for a simpler class / property signature if the index only needs to be an int, but it also allows for non int indexes.

Also provides both a read/write implementation and a read-only implementation based on your needs.

Caveat I found after trying to use this: The shortcut for int index only class signature. It apparently still needs the full signature for the property.

IE:

public MyClass : IIndexedProperty<string>
{
    public IIndexedProperty<string> MyString => this;
    string IIndexedPropety<int, string>.this[int index] { get; set; }
}
B.O.B.
  • 704
  • 4
  • 10