Ответ 1
Ну, проще всего вернуть свойство объект, реализующий IList
.
Помните, что только потому, что он реализует IList, это не значит, что это сама коллекция, просто она реализует определенные методы.
В С# я считаю проиндексированные свойства чрезвычайно полезными. Например:
var myObj = new MyClass();
myObj[42] = "hello";
Console.WriteLine(myObj[42]);
Однако, насколько я знаю, нет синтаксического сахара для поддержки полей, которые сами поддерживают индексацию (пожалуйста, исправьте меня, если я ошибаюсь). Например:
var myObj = new MyClass();
myObj.field[42] = "hello";
Console.WriteLine(myObj.field[42]);
Причина, по которой мне это нужно, заключается в том, что я уже использую свойство index в своем классе, но у меня есть функции GetNumX()
, GetX()
и SetX()
следующим образом:
public int NumTargetSlots {
get { return _Maker.NumRefs; }
}
public ReferenceTarget GetTarget(int n) {
return ReferenceTarget.Create(_Maker.GetReference(n));
}
public void SetTarget(int n, ReferenceTarget rt) {
_Maker.ReplaceReference(n, rt._Target, true);
}
Как вы, вероятно, видите, экспонирование их как одного индексируемого свойства поля имело бы больше смысла. Я мог бы написать собственный класс, чтобы достичь этого каждый раз, когда я хочу синтаксический сахар, но весь стандартный код просто кажется ненужным.
Поэтому я написал собственный класс для инкапсуляции шаблона и облегчения создания свойств, которые можно проиндексировать. Таким образом, я могу добавить новое свойство следующим образом:
public IndexedProperty<ReferenceTarget> TargetArray {
get {
return new IndexedProperty<int, ReferenceTarget>(
(int n) => GetTarget(n),
(int n, ReferenceTarget rt) => SetTarget(n, rt));
}
}
Код для этого нового класса IndexedProperty выглядит следующим образом:
public class IndexedProperty<IndexT, ValueT>
{
Action<IndexT, ValueT> setAction;
Func<IndexT, ValueT> getFunc;
public IndexedProperty(Func<IndexT, ValueT> getFunc, Action<IndexT, ValueT> setAction)
{
this.getFunc = getFunc;
this.setAction = setAction;
}
public ValueT this[IndexT i]
{
get {
return getFunc(i);
}
set {
setAction(i, value);
}
}
}
Итак, мой вопрос: есть ли лучший способ сделать все это?
Если говорить точнее, есть ли в С# более идиоматический способ создания свойства индексируемого поля, и если нет, то как я могу улучшить свой класс IndexedProperty
?
ОБНОВЛЕНИЕ: После дальнейшего исследования Джон Скит называет это "именованным индексатором".
Ну, проще всего вернуть свойство объект, реализующий IList
.
Помните, что только потому, что он реализует IList, это не значит, что это сама коллекция, просто она реализует определенные методы.
Я нашел вашу идею полезной, поэтому я расширил ее. Технически это может быть неправильный ответ, поскольку я не уверен, что он прямо отвечает на ваш вопрос, но я подумал, что он может быть полезен для людей, которые пришли сюда в поисках индексаторов собственности.
Во-первых, мне нужно было иметь возможность поддерживать свойства get-only и set-only, поэтому я немного изменил ваш код для этих сценариев:
Получить и установить (очень незначительные изменения):
public class IndexedProperty<TIndex, TValue>
{
readonly Action<TIndex, TValue> SetAction;
readonly Func<TIndex, TValue> GetFunc;
public IndexedProperty(Func<TIndex, TValue> getFunc, Action<TIndex, TValue> setAction)
{
this.GetFunc = getFunc;
this.SetAction = setAction;
}
public TValue this[TIndex i]
{
get
{
return GetFunc(i);
}
set
{
SetAction(i, value);
}
}
}
Get Only:
public class ReadOnlyIndexedProperty<TIndex, TValue>
{
readonly Func<TIndex, TValue> GetFunc;
public ReadOnlyIndexedProperty(Func<TIndex, TValue> getFunc)
{
this.GetFunc = getFunc;
}
public TValue this[TIndex i]
{
get
{
return GetFunc(i);
}
}
}
Установить только:
public class WriteOnlyIndexedProperty<TIndex, TValue>
{
readonly Action<TIndex, TValue> SetAction;
public WriteOnlyIndexedProperty(Action<TIndex, TValue> setAction)
{
this.SetAction = setAction;
}
public TValue this[TIndex i]
{
set
{
SetAction(i, value);
}
}
}
ПримерExample
Вот простой пример использования. Я наследую от Collection и создаю именованный индексатор, как его назвал Джон Скит. Этот пример должен быть простым, а не практичным:
public class ExampleCollection<T> : Collection<T>
{
public IndexedProperty<int, T> ExampleProperty
{
get
{
return new IndexedProperty<int, T>(GetIndex, SetIndex);
}
}
private T GetIndex(int index)
{
return this[index];
}
private void SetIndex(int index, T value)
{
this[index] = value;
}
}
ExampleCollection in the Wild
Этот наспех построенный unit тест показывает, как он выглядит, когда вы ExampleCollection в проекте:
[TestClass]
public class IndexPropertyTests
{
[TestMethod]
public void IndexPropertyTest()
{
var MyExample = new ExampleCollection<string>();
MyExample.Add("a");
MyExample.Add("b");
Assert.IsTrue(MyExample.ExampleProperty[0] == "a");
Assert.IsTrue(MyExample.ExampleProperty[1] == "b");
MyExample.ExampleProperty[0] = "c";
Assert.IsTrue(MyExample.ExampleProperty[0] == "c");
}
}
Наконец, если вы хотите использовать версии только для получения и только для установки, это выглядит следующим образом:
public ReadOnlyIndexedProperty<int, T> ExampleProperty
{
get
{
return new ReadOnlyIndexedProperty<int, T>(GetIndex);
}
}
Или:
public WriteOnlyIndexedProperty<int, T> ExampleProperty
{
get
{
return new WriteOnlyIndexedProperty<int, T>(SetIndex);
}
}
В обоих случаях результат работает так, как вы ожидаете, что будет работать только свойство get-only/set-only.
Я думаю, что дизайн, который вы опубликовали, - это способ пойти, с той разницей, что я бы определил интерфейс:
public interface IIndexed<IndexT, ValueT>
{
ValueT this[IndexT i] { get; set; }
}
И для обычных случаев я бы использовал класс, который вы ввели в исходный вопрос (который будет реализовывать этот интерфейс).
Было бы неплохо, если бы библиотека базового класса предоставила нам подходящий интерфейс, но это не так. Возвращение IList здесь было бы извращением.
Это не отвечает на ваш вопрос, но интересно отметить, что CIL поддерживает создание таких свойств, как вы описали - некоторые языки (например, F #) позволят вам также определить их таким образом.
Индексатор this[]
в С# - это всего лишь конкретный экземпляр одного из них, который при создании вашего приложения переименовывается в Item
. Компилятор С# знает, как это прочитать, поэтому, если вы напишете "именованный индексатор" под названием Target
в библиотеке F # и попробуйте использовать его на С#, единственный способ получить доступ к этому свойству - через ... get_Target(int)
и void set_Target(int, ...)
. Отстой.
Почему бы вам не наследовать класс IList, тогда вы можете просто использовать индекс и добавить к нему свои собственные свойства. Хотя у вас все еще есть функции "Добавить и удалить", это не является нечестным, чтобы не использовать их. Кроме того, вам может показаться полезным, чтобы они прошли по дороге.
Дополнительные сведения о списках и массивах: Что лучше использовать массив или List < gt;?
EDIT:
В MSDN есть статья о свойствах индекса, которую вы можете посмотреть. Кажется, это не сложно, просто утомительно.
http://msdn.microsoft.com/en-us/library/aa288464(VS.71).aspx
Существует еще один вариант, когда вы можете создать альтернативный метод Add, но в зависимости от типа объекта метод добавления может не всегда вызываться. Разъясняется здесь:
Как переопределить метод добавления List <T> на С#?
EDIT 2: Как и в первом сообщении
Почему у вас нет скрытого объекта списка в вашем классе, а затем просто создайте свои собственные методы для получения данных. Таким образом, добавление и удаление не отображаются, и список уже проиндексирован.
Также, что вы подразумеваете под "named indexer", вы ищете эквивалент строки [ "My_Column_Name" ]. Это статья MSDN, которую я нашел, которая может быть полезна, поскольку она показывает основной способ реализации этого свойства.
http://msdn.microsoft.com/en-us/library/146h6tk5.aspx
class Test
{
private List<T> index;
public T this[string name]{ get; set; }
public T this[int i]
{
get
{
return index[i];
}
set
{
index[i] = value;
}
}
}
После некоторых исследований я придумал немного другое решение, которое лучше соответствовало моим потребностям. Пример немного придуман, но он подходит для того, что мне нужно, чтобы адаптировать его.
Использование:
MyClass MC = new MyClass();
int x = MC.IntProperty[5];
И код, чтобы заставить его работать:
public class MyClass
{
public readonly IntIndexing IntProperty;
public MyClass()
{
IntProperty = new IntIndexing(this);
}
private int GetInt(int index)
{
switch (index)
{
case 1:
return 56;
case 2:
return 47;
case 3:
return 88;
case 4:
return 12;
case 5:
return 32;
default:
return -1;
}
}
public class IntIndexing
{
private MyClass MC;
internal IntIndexing(MyClass mc)
{
MC = mc;
}
public int this[int index]
{
get { return MC.GetInt(index); }
}
}
}
Попробуйте явно реализованные интерфейсы, как показано на втором пути, предложенном в ответе здесь: Именованное индексированное свойство в С#?