Именованное индексируемое свойство в 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;
}
}
}
Но (за исключением того, что это фактически решает проблему), это решение вводит в основном только минусы:
- Это приводит к загрязнению кода (возможно) большим количеством небольших прокси-классов.
- Представленное решение немного нарушает инкапсуляцию (внутренний класс обращается к закрытым членам внешнего класса), лучше передать экземпляр списка
MyPropProxy
Ctor, что потребует еще больше кода. - Предоставление внутренних вспомогательных классов - это не то, что я бы предложил. Это можно решить, введя дополнительный интерфейс, но это еще одна сущность, которую нужно реализовать (протестировать, поддерживать и т. Д.)
Есть и другой способ. Это также немного загрязняет код, но, конечно, намного меньше, чем предыдущий:
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; }
}