2010-07-27 13 views
24

W języku C# znajduję bardzo przydatne narzędzie indexed properties. Na przykład:Łatwe tworzenie właściwości, które obsługują indeksowanie w języku C#

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

Jednak o ile wiem, nie ma cukier syntaktyczny wspierać obszary, które same obsługują 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 muszę to, że jestem już za pomocą właściwości indeksu w mojej klasie, ale mam GetNumX(), GetX() i SetX() funkcje w następujący sposób:

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 ty prawdopodobnie widzą, że eksponowanie ich jako jednej właściwości pola indeksowego miałoby więcej sensu. Mógłbym napisać niestandardową klasę, aby osiągnąć ten cel za każdym razem, gdy chcę uzyskać cukier syntaktyczny, ale cały kod tylko wydaje się niepotrzebny.

Tak więc napisałem niestandardową klasę, aby hermetyzować szablon i ułatwić tworzenie właściwości, które można indeksować. W ten sposób mogę dodać nową właściwość następująco:

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

kod dla tej 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 istnieje lepszy sposób zrobić wszystko to?

Cóż konkretnie, czy istnieje bardziej idiomatyczny sposób w języku C# do tworzenia indeksowalnej właściwości pola, a jeśli nie, to jak mogę poprawić moją klasę IndexedProperty?

EDYCJA: Po dalszych badaniach Jon Skeet nazywa to "named indexer".

+0

Sprawdź http://stackoverflow.com/questions/23081402/named-indexed-property-in-c/23081403#23081403 – Spook

Odpowiedz

6

Cóż, najprościej jest, aby właściwość zwróciła obiekt, który implementuje IList.

Pamiętaj, że to, że implementuje ILIST, nie oznacza, że ​​jest to sama kolekcja, tylko że implementuje określone metody.

+0

Hmmmm ... Podoba mi się ten sposób myślenia. Jednak problem z IList dotyczy członków takich jak "Dodaj", "Usuń", itp. Byłoby nieuczciwe, aby ujawnić właściwość jako IList, po prostu je zignoruj. Być może jest inny interfejs, który ma więcej sensu? – cdiggins

+3

Możesz mieć "IList" tylko do odczytu. 'Array' i' ReadOnlyCollection 'oba implementują' IList'; obie te klasy zgłaszają wyjątki, gdy wywołasz 'Dodaj',' Usuń', itp. –

+0

Ale nie jest to IList "tylko do odczytu". Możesz ustawić elementy w poszczególnych indeksach. Nie jest to wyraźnie lista, ale nie jest to tablica. Jednak nie wiedziałem, że "Array" zaimplementował "IList"! Wygląda na to, że decyzja jest zła. Oczywiście będzie teraz klasyfikowany jako "idiomatyczne C#". Jestem na płocie. – cdiggins

3

Dlaczego twoja klasa nie odziedziczy ILista, wtedy możesz po prostu użyć indeksu i dodać do niego swoje właściwości. Chociaż nadal będziesz mieć funkcje dodawania i usuwania, nie będziesz nieuczciwy, aby ich nie używać. Poza tym może ci się przydać, że mają je przy sobie.

Aby uzyskać więcej informacji na temat list i tablic sprawdź: Which is better to use array or List<>?

EDIT:

MSDN ma artykuł na temat właściwości indeksu może chcesz przyjrzeć. Nie wydaje się skomplikowane tylko nudne.

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

Nie ma innej opcji, gdzie można stworzyć alternatywę Dodaj metodę, ale w zależności od rodzaju obiektu metoda add nie zawsze może być wywołana.Wyjaśnione tutaj:

How do I override List<T>'s Add method in C#?

EDIT 2: Podobny do pierwszego postu

Dlaczego nie masz ukryty obiekt listy w swojej klasie, a potem po prostu tworzyć własne metody pozyskiwania danych . W ten sposób opcje Dodaj i Usuń nie są widoczne, a lista jest już zindeksowana.

Co masz na myśli przez "nazwany indeksator", czy szukasz odpowiednika wiersza ["Moja_nazwa_kolumny"]. Jest tam artykuł MSDN, który znalazłem, może być przydatny, ponieważ wydaje się, że pokazuje podstawowy sposób wdrożenia 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; 
      } 
     } 
    } 
+0

Chcę, aby moje typy wskazywały zamierzone użycie. Problem z używaniem IList polega na tym, że muszę dodać dokumentację dla wszystkich użytkowników mojego kodu, mówiąc tak, zaimplementuję IList, ale nie powinieneś używać "Dodaj" lub "Usuń". Wolę autodokumentowanie kodu. – cdiggins

+1

@cdiggins Mam dwa zdanie na ten temat.Zgadzam się z tobą w zasadzie, ale dla 'IList' i' ICollection', dokumentacja ramowa wyraźnie zauważa, że ​​klasy implementujące powinny wyrzucać 'NotSupportedException' przy wywołaniu' Add', 'Clear',' Insert', 'Remove' lub 'RemoveAt' metod, jeśli kolekcja jest tylko do odczytu (tj. jeśli właściwość' IsReadOnly' zwraca wartość true). Rozsądnie jest oczekiwać, że użytkownicy będą używać twojego kodu w sposób zgodny z dokumentacją, więc myślę, że użycie 'IList' jest rozsądne - i jest to rzeczywiście zalecany sposób robienia tego, co chcesz. – Dathan

+0

Hej Gage, już linkowałem do tego artykułu MSDN w moim poście. – cdiggins

5

Myślę, że projekt już wysłane jest droga, z tą różnicą, że chciałbym zdefiniować interfejs:

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

oraz typowych przypadkach, ja użyłaby klasy, którą umieściłeś w oryginalnym pytaniu (która implementowałaby ten interfejs).

Byłoby miło, gdyby biblioteka klasy podstawowej dostarczyła nam odpowiedni interfejs, ale tak nie jest. Zwrócenie tu ILista byłoby perwersją.

4

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

Indeksujący w C# jest tylko konkretnym wystąpieniem jednego z nich, którego nazwa zmienia się na Item podczas budowania aplikacji. Kompilator C# wie tylko, jak go odczytać, więc jeśli napiszesz "nazwany indeksator" o nazwie Target w bibliotece F # i spróbujesz go użyć w C#, jedynym sposobem uzyskania dostępu do właściwości jest ... get_Target(int) i void set_Target(int, ...) metody. Do kitu.

7

To nie jest technicznie poprawny sposób użycia StackOverflow, ale uznałem, że twój pomysł jest tak przydatny, że go rozszerzyłem. Pomyślałem, że zaoszczędziłoby to czasu ludziom, gdybym opublikował to, co wymyśliłem i przykład, jak z niego korzystać.

Po pierwsze, muszę być w stanie utrzymać się tylko i set-tylko właściwości, więc zrobiłem lekką odmianę kodzie dla tych scenariuszy:

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

dostać tylko:

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

ustawić 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. Odziedziczę po kolekcji i utworzę nazwany indeksator, jak to nazwał Jon Skeet.Przykład ten 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 na Dzikim

tego pochopnie skonstruowanej testów jednostkowych pokazuje jak to wygląda, gdy ExampleCollection 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"); 

    } 
} 

Wreszcie, jeśli chcesz użyć wersji tylko do pobrania i tylko z ustawieniem, wygląda to następująco:

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 się spodziewać tylko get/set tylko nieruchomość zachowywać.

+1

niepotrzebnie naciskasz na GC z powtarzającą się 'nową IndexedProperty' w geterze własności. Również korzystanie z delegatów prawdopodobnie zapobiega wielu optymalizacji. Oba mogą być dopuszczalne w niektórych przypadkach użycia, ale generalnie nie polecam takiego kodu. Pisanie wyspecjalizowanych klas indeksujących z pewnością jest więcej pracy, ale będzie działać znacznie lepiej. –

+0

Twój przykład jest mylący, ponieważ uzyskujesz dostęp do kolekcji, której elementy można już było uzyskać bez niestandardowej klasy indeksującej. Lepiej użyj 'class CExample {private string [] m_Texts; prywatny ciąg GetText (indeks int) {return m_Texts [indeks];} 'aby zademonstrować typowe użycie. –

2

Po przeprowadzeniu badań opracowałem nieco inne rozwiązanie, które lepiej pasuje do moich potrzeb. Ten przykład jest trochę wymyślony, ale pasuje do tego, czego potrzebuję, aby go dostosować.

Zastosowanie:

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

Tego właśnie szukałem. Kompaktowy. – MrHIDEn

Powiązane problemy