Именованное индексируемое свойство в C#?

В нескольких языках, таких как Delphi, есть очень удобный способ создания индексаторов: можно индексировать не только весь класс, но даже отдельные свойства, например:

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;

Это можно легко использовать:

var c : TMyClass;

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

Есть ли способ легко добиться того же эффекта в C#?

2 ответа

Решение

Хорошо известным решением является создание прокси-класса:

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

Но (за исключением того, что это фактически решает проблему), это решение вводит в основном только минусы:

  • Это приводит к загрязнению кода (возможно) большим количеством небольших прокси-классов.
  • Представленное решение немного нарушает инкапсуляцию (внутренний класс обращается к закрытым членам внешнего класса), лучше передать экземпляр списка MyPropProxyCtor, что потребует еще больше кода.
  • Предоставление внутренних вспомогательных классов - это не то, что я бы предложил. Это можно решить, введя дополнительный интерфейс, но это еще одна сущность, которую нужно реализовать (протестировать, поддерживать и т. Д.)

Есть и другой способ. Это также немного загрязняет код, но, конечно, намного меньше, чем предыдущий:

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

Плюсы:

  • Никакие прокси-классы (которые занимают место в памяти, служат только одной цели и (в самом простом решении) нарушают инкапсуляцию.
  • Простое решение, требует небольшого кода для добавления другого индексированного свойства

Минусы:

  • Каждое свойство требует другого открытого интерфейса
  • С увеличением индексированных свойств класс реализует все больше и больше интерфейсов

Это самый простой (с точки зрения длины и сложности кода) способ введения индексированных свойств в C#. Если, конечно, кто-то не публикует сообщения еще короче и проще.

Это основано на комментарии HB, но немного расширено и представлено в виде блока кода (что упрощает копирование):

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

Здесь используются коварианты из C# 9.0, поэтому, если вы используете более старую версию, удалите а также заявления.

Плюсы: большинство распространенных индексированных свойств используют простой индекс, поэтому это позволяет использовать более простую подпись класса/свойства, если индекс должен быть только , но также допускает не индексы.

Также предоставляет как реализацию для чтения/записи, так и реализацию только для чтения в зависимости от ваших потребностей.

Предостережение, которое я обнаружил после попытки использовать это: ярлык для индексировать только подпись класса. По-видимому, ему все еще нужна полная подпись для свойства.

IE:

      public MyClass : IIndexedProperty<string>
{
    public IIndexedProperty<string> MyString => this;
    string IIndexedPropety<int, string>.this[int index] { get; set; }
}
Другие вопросы по тегам