Простое создание свойств, поддерживающих индексацию в C # - PullRequest
26 голосов
/ 27 июля 2010

В C # я нахожу индексированные свойства чрезвычайно полезными.Например:

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

Так что мой вопрос: есть лилучший способ сделать все это ?

Если говорить точнее, есть ли в C # более идиоматический способ создания свойства индексируемого поля, и если нет, то как я могу улучшить свой класс IndexedProperty?

РЕДАКТИРОВАТЬ: После дальнейших исследований Джон Скит называет это " именованным индексатором ".

Ответы [ 7 ]

9 голосов
/ 12 июля 2013

Технически это не правильный способ использования StackOverflow, но я посчитал вашу идею настолько полезной, что расширил ее. Я думал, что это сэкономит людям время, если я опубликую то, что я придумал, и пример того, как его использовать.

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

Получить и установить (очень незначительные изменения):

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

Пример * ** тысяча двадцать-один * тысяча двадцать два Вот простой пример использования. Я наследую от 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

Этот наспех построенный модульный тест показывает, как он выглядит, когда вы 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.

8 голосов
/ 27 июля 2010

Ну, самое простое - это свойство вернуть объект, который реализует IList.

Помните, что только то, что он реализует IList, не означает, что это сама коллекция, просто то, что он реализует определенные методы.

7 голосов
/ 27 июля 2010

Я думаю, что дизайн, который вы разместили, - это путь, с тем отличием, что я бы определил интерфейс:

public interface IIndexed<IndexT, ValueT>
{
    ValueT this[IndexT i] { get; set; }
}

А для обычных случаев я бы использовал класс, который вы задали в исходном вопросе (который бы реализовывал этот интерфейс).

Было бы неплохо, если бы библиотека базовых классов предоставила нам подходящий интерфейс, но это не так. Возвращение IList здесь было бы извращением.

4 голосов
/ 27 июля 2010

Это не отвечает на ваш вопрос, но интересно отметить, что CIL поддерживает создание свойств, как вы описали - некоторые языки (например, F #) позволяют вам определять их также таким образом.

Индексатор this[] в C # - это просто конкретный экземпляр одного из них, который переименовывается в Item при сборке приложения.Компилятор C # знает, как его читать, поэтому, если вы напишите «именованный индексатор» с именем Target в библиотеке F # и попытаетесь использовать его в C #, единственный способ получить доступ к свойству - через ... get_Target(int) и void set_Target(int, ...) методы.Отстой.

3 голосов
/ 27 июля 2010

Почему бы вашему классу не наследовать IList, тогда вы можете просто использовать индекс и добавить к нему свои собственные свойства. Хотя у вас все еще есть функции «Добавить и удалить», их нечестно не использовать. Кроме того, вам может быть полезно иметь их в дальнейшем.

Для получения дополнительной информации о списках и массивах проверьте: Что лучше использовать массив или список <>?

EDIT:

В MSDN есть статья о свойствах индекса, на которую вы можете взглянуть. Не кажется сложным, просто утомительно.

http://msdn.microsoft.com/en-us/library/aa288464(VS.71).aspx

Существует еще одна опция, в которой вы можете создать альтернативный метод Add, но в зависимости от типа объекта ваш метод add может вызываться не всегда. Объясняется здесь:

Как переопределить метод Add List в C #?

РЕДАКТИРОВАТЬ 2: Аналогично первому сообщению

Почему у вас нет скрытого объекта списка в вашем классе, а затем просто создайте свои собственные методы для получения данных. Таким образом, «Добавить» и «Удалить» не отображаются, и список уже проиндексирован.

Также, что вы подразумеваете под "именованным индексатором", вы ищете эквивалент строки ["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;
            }
        }
    }
2 голосов
/ 24 февраля 2015

После некоторых исследований я нашел немного другое решение, которое лучше соответствовало моим потребностям. Пример немного придуман, но он подходит для того, что мне нужно, чтобы адаптировать его.

Использование:

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); }
        }
    }
}
0 голосов
/ 30 мая 2016

Попробуйте явно реализованные интерфейсы, как показано во втором способе, предложенном в ответе здесь: Именованное индексированное свойство в C #?

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...