Łatwe tworzenie właściwości obsługujących indeksowanie w C#

W C# znajduję indeksowane właściwości niezwykle przydatne. Na przykład:

var myObj = new MyClass();
myObj[42] = "hello"; 
Console.WriteLine(myObj[42]);

Jednak o ile wiem, nie ma cukru składniowego do obsługi pól, które same wspierają indeksowanie(Proszę mnie poprawić, jeśli się mylę). Na przykład:

var myObj = new MyClass();
myObj.field[42] = "hello"; 
Console.WriteLine(myObj.field[42]);

Powodem, dla którego tego potrzebuję, jest to, że już używam właściwości index na mojej klasie, ale mam GetNumX(), GetX(), and SetX() functions as following:

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

Jak zapewne widzisz eksponując je jako jedno indeksowalne pole własność miałaby więcej sensu. Mógłbym napisać niestandardową klasę, aby to osiągnąć za każdym razem, gdy chcę cukru składniowego, ale cały kod boilerplate wydaje się niepotrzebny.

Więc napisałem niestandardową klasę do hermetyzacji kotła i aby ułatwić tworzenie właściwości, które mogą być indeksowane . W ten sposób mogę dodać nową właściwość w następujący sposób:

public IndexedProperty<ReferenceTarget> TargetArray  {
    get { 
       return new IndexedProperty<int, ReferenceTarget>(
           (int n) => GetTarget(n), 
           (int n, ReferenceTarget rt) => SetTarget(n, rt));
       }
}

Kod nowej klasy IndexedProperty wygląda następująco:

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

Więc moje pytanie brzmi: czy jest lepszy sposób, aby to wszystko ?

Mówiąc dokładniej, czy istnieje bardziej idiomatyczny sposób w C#, aby utworzyć indeksowalną właściwość pola, a jeśli nie, to jak mógłbym ulepszyć moją klasę IndexedProperty?

EDIT: po dalszych badaniach Jon Skeet nazywa to " nazwany indexer".

Author: Community, 2010-07-27

7 answers

Well, the simpliest is to have the property return an object which implements IList.

Pamiętaj, że to, że implementuje IList, nie oznacza, że jest to zbiór sam w sobie, tylko, że implementuje pewne metody.

 6
Author: James Curran,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-07-27 14:30:40

Technicznie nie jest to poprawny sposób użycia StackOverflow, ale uznałem twój pomysł za tak przydatny, że go rozszerzyłem. Pomyślałem, że zaoszczędzi to ludziom czasu, jeśli opublikuję to, co wymyśliłem i przykład, jak z tego korzystać.

Po pierwsze, musiałem być w stanie obsługiwać właściwości get-only I set-only, więc zrobiłem niewielką zmianę kodu dla tych scenariuszy:

Get and Set (bardzo drobne zmiany):

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

Set Tylko:

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

Przykład

Oto prosty przykład użycia. Dziedziczę z kolekcji i tworzę nazwane indeksery, jak nazywał to Jon Skeet. Ten przykład ma być prosty, a nie praktyczny:

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

Ten pospiesznie skonstruowany test jednostkowy pokazuje, jak to wygląda, gdy przykładasz kolekcję w projekcie:

[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");

    }
}

Na koniec, jeśli chcesz użyć wersji get-only I set-only, wygląda to tak: to:

    public ReadOnlyIndexedProperty<int, T> ExampleProperty
    {
        get
        {
            return new ReadOnlyIndexedProperty<int, T>(GetIndex);
        }
    }

Lub:

    public WriteOnlyIndexedProperty<int, T> ExampleProperty
    {
        get
        {
            return new WriteOnlyIndexedProperty<int, T>(SetIndex);
        }
    }

W obu przypadkach wynik działa dokładnie tak, jak można oczekiwać zachowania właściwości get-only/set-only.

 9
Author: Brian MacKay,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-07-12 15:53:08

Myślę, że projekt, który napisałeś, jest drogą do zrobienia, z tą jedną różnicą, że zdefiniowałbym Interfejs:

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

I dla typowych przypadków, użyłbym klasy, którą umieściłeś w oryginalnym pytaniu(która zaimplementowałaby ten interfejs).

Byłoby miło, gdyby biblioteka klasy bazowej zapewniła nam odpowiedni interfejs, ale tak nie jest. zwracanie IList tutaj byłoby wypaczeniem.

 6
Author: quentin-starin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-07-27 18:00:18

To nie odpowiada na twoje pytanie, ale warto zauważyć, że CIL obsługuje tworzenie właściwości takich, jak już opisałeś - niektóre języki (na przykład F#) pozwolą Ci je zdefiniować w taki sposób.

Indeksator this[] W C# jest tylko specyficzną instancją jednego z nich, który zmienia nazwę na Item Podczas tworzenia aplikacji. Kompilator C# wie tylko jak go odczytać, więc jeśli napiszesz "named indexer" o nazwie Target w bibliotece F# i spróbujesz go użyć w C#, jedynym sposobem możesz uzyskać dostęp do właściwości za pomocą metod ... get_Target(int) i void set_Target(int, ...). Do bani.

 4
Author: Mark H,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-07-27 18:16:52

Dlaczego twoja klasa nie odziedziczy IList, wtedy możesz po prostu użyć indeksu i dodać do niego własne właściwości. Chociaż nadal będziesz mieć funkcje dodawania i usuwania, nie jest nieuczciwe, aby ich nie używać. Plus może okazać się przydatne, aby mieć je dalej w dół drogi.

Aby uzyskać więcej informacji na temat List i tablic sprawdź: która jest lepsza do użycia array lub List?

EDIT:

MSDN ma artykuł o właściwościach indeksu, na który warto spojrzeć. Nie wydaje się skomplikowane, tylko nudne.

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

Istnieje inna opcja, w której można utworzyć alternatywną metodę Add, ale w zależności od typu obiektu metoda add nie zawsze może być wywołana. Wyjaśnione tutaj:

Jak nadpisać metodę dodawania List w C#?

EDIT 2: podobne do pierwszego posta

Dlaczego nie masz obiektu ukrytej listy w swojej klasie, a następnie wystarczy stworzyć własne metody pozyskiwania danych. W ten sposób dodawanie i usuwanie nie są widoczne, a lista jest już zindeksowana.

Również co masz na myśli przez "named indexer" szukasz odpowiednika wiersza ["My_Column_Name"]. Znajduje się artykuł MSDN, który może być przydatny, ponieważ wydaje się pokazywać podstawowy sposób implementacji tej właściwości.

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;
            }
        }
    }
 3
Author: Gage,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:09:29

Po kilku badaniach, wymyśliłem nieco inne rozwiązanie, które lepiej pasowało do moich potrzeb. Przykład jest trochę wymyślony, ale pasuje do tego, czego potrzebuję, aby go dostosować.

Użycie:

MyClass MC = new MyClass();
int x = MC.IntProperty[5];

I kod aby to działało:

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); }
        }
    }
}
 2
Author: damichab,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-02-24 13:18:58

Wypróbuj jawnie zaimplementowane interfejsy, jak pokazano na 2. sposobie zaproponowanym w odpowiedzi tutaj: nazwana indeksowana właściwość w C#?

 0
Author: George Birbilis,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:00:14