Question

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?

Était-ce utile?

La solution

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.

Autres conseils

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; }
}
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top