Dlaczego C # nie implementuje indeksowanych właściwości?

83

Wiem, wiem ... Odpowiedź Erica Lipperta na tego typu pytanie brzmi zwykle mniej więcej w stylu: „ ponieważ nie było warto zaprojektować, wdrożyć, przetestować i udokumentować ”.

Mimo to chciałbym uzyskać lepsze wyjaśnienie ... Czytałem ten wpis na blogu o nowych funkcjach C # 4 , aw sekcji o COM Interop moją uwagę przykuła następująca część:

Nawiasem mówiąc, ten kod wykorzystuje jeszcze jedną nową funkcję: indeksowane właściwości (przyjrzyj się bliżej tym nawiasom kwadratowym po polu Zakres). Ale ta funkcja jest dostępna tylko dla współdziałania COM; nie można tworzyć własnych indeksowanych właściwości w C # 4.0 .

Dobrze ale dlaczego ? Wiedziałem już i żałowałem, że nie można utworzyć indeksowanych właściwości w C #, ale to zdanie skłoniło mnie do ponownego przemyślenia. Widzę kilka dobrych powodów, aby to wdrożyć:

  • CLR to obsługuje (na przykład PropertyInfo.GetValuema indexparametr), więc szkoda, że ​​nie możemy tego wykorzystać w C #
  • jest obsługiwany w przypadku współpracy z COM, jak pokazano w artykule (przy użyciu dynamicznej wysyłki)
  • jest zaimplementowany w VB.NET
  • jest już możliwe tworzenie indeksatorów, tj. zastosowanie indeksu do samego obiektu, więc prawdopodobnie nie byłoby wielkim problemem rozszerzenie pomysłu na właściwości, zachowując tę ​​samą składnię i zastępując po prostu thisnazwą właściwości

Pozwoliłoby napisać takie rzeczy:

public class Foo
{
    private string[] _values = new string[3];
    public string Values[int index]
    {
        get { return _values[index]; }
        set { _values[index] = value; }
    }
}

Obecnie jedynym znanym mi obejściem jest utworzenie klasy wewnętrznej ( ValuesCollectionna przykład), która implementuje indeksator i zmiana Valueswłaściwości, tak aby zwracała instancję tej klasy wewnętrznej.

Jest to bardzo łatwe, ale irytujące ... Więc może kompilator mógłby to zrobić za nas! Opcją byłoby wygenerowanie klasy wewnętrznej, która implementuje indeksator i ujawnienie jej za pośrednictwem publicznego interfejsu ogólnego:

// interface defined in the namespace System
public interface IIndexer<TIndex, TValue>
{
    TValue this[TIndex index]  { get; set; }
}

public class Foo
{
    private string[] _values = new string[3];

    private class <>c__DisplayClass1 : IIndexer<int, string>
    {
        private Foo _foo;
        public <>c__DisplayClass1(Foo foo)
        {
            _foo = foo;
        }

        public string this[int index]
        {
            get { return _foo._values[index]; }
            set { _foo._values[index] = value; }
        }
    }

    private IIndexer<int, string> <>f__valuesIndexer;
    public IIndexer<int, string> Values
    {
        get
        {
            if (<>f__valuesIndexer == null)
                <>f__valuesIndexer = new <>c__DisplayClass1(this);
            return <>f__valuesIndexer;
        }
    }
}

Ale oczywiście w takim przypadku właściwość faktycznie zwróciłaby a IIndexer<int, string>, a tak naprawdę nie byłaby właściwością indeksowaną ... Lepiej byłoby wygenerować rzeczywistą właściwość indeksowaną w środowisku CLR.

Co myślisz ? Czy chcesz zobaczyć tę funkcję w języku C #? Jeśli nie, dlaczego?

Thomas Levesque
źródło
1
Wydaje mi się, że to kolejna z tych kwestii „otrzymujemy prośby o X, ale nie więcej niż o Y” .
ChaosPandion
1
@ChaosPandion, tak, masz rację ... Ale ta funkcja będzie prawdopodobnie dość łatwe do wykonania, a mimo to na pewno nie jest „must have”, to z pewnością należy do „miło mieć” kategoria
Thomas Levesque
4
Indeksatory są już nieco irytujące z punktu widzenia CLR. Dodają nowy przypadek graniczny do kodu, który chce pracować z właściwościami, ponieważ teraz każda właściwość może potencjalnie mieć parametry indeksatora. Myślę, że implementacja C # ma sens, ponieważ koncepcja, którą zwykle reprezentuje indeksator, nie jest właściwością obiektu, ale raczej jego „zawartością”. Jeśli podasz dowolne właściwości indeksatora, sugerujesz, że klasa może mieć różne grupy treści, co w naturalny sposób prowadzi do hermetyzacji złożonej treści podrzędnej jako nowej klasy. Moje pytanie brzmi: dlaczego środowisko CLR zapewnia indeksowane właściwości?
Dan Bryant
1
@tk_ dzięki za konstruktywny komentarz. Czy publikujesz podobne komentarze do wszystkich postów dotyczących języków, które nie są Free Pascal? Cóż, mam nadzieję, że dzięki temu poczujesz się dobrze ...
Thomas Levesque
3
Jest to jedna z niewielu sytuacji, w których C ++ / CLI i VB.net są lepsze niż C #. Zaimplementowałem wiele indeksowanych właściwości w moim kodzie C ++ / CLI, a teraz konwertując go na C #, muszę znaleźć obejścia dla nich wszystkich. :-( SUCKS !!! // Twoje To pozwoliłoby napisać takie rzeczy, co robiłem przez lata.
Tobias Knauss

Odpowiedzi:

122

Oto, jak zaprojektowaliśmy C # 4.

Najpierw sporządziliśmy listę wszystkich możliwych funkcji, które moglibyśmy dodać do języka.

Następnie podzieliliśmy funkcje na „to jest złe, nigdy nie możemy tego robić”, „to jest niesamowite, musimy to zrobić” i „to jest dobre, ale nie róbmy tego tym razem”.

Następnie przyjrzeliśmy się, jaki budżet mieliśmy na zaprojektowanie, wdrożenie, przetestowanie, udokumentowanie, dostarczenie i utrzymanie funkcji „trzeba mieć” i odkryliśmy, że przekroczyliśmy budżet o 100%.

Więc przenieśliśmy kilka rzeczy z wiadra „muszę mieć” do wiadra „miło mieć”.

Indeksowane właściwości były nigdy nigdzie w pobliżu górnej części listy „muszę mieć”. Są bardzo nisko na liście „fajnych” i flirtują z listą „złych pomysłów”.

Każda minuta, którą spędzamy na projektowaniu, wdrażaniu, testowaniu, dokumentowaniu lub utrzymywaniu fajnej funkcji X to minuta, której nie możemy poświęcić na niesamowite funkcje A, B, C, D, E, F i G. Musimy bezwzględnie ustalić priorytety, abyśmy tylko zrobić najlepsze możliwe funkcje. Indeksowane właściwości byłyby fajne, ale ładne nie są nawet wystarczająco dobre, aby faktycznie zostały zaimplementowane.

Eric Lippert
źródło
20
Czy mogę zagłosować za umieszczeniem go na liście złych? Naprawdę nie rozumiem, jak obecna implementacja jest dużym ograniczeniem, skoro można po prostu ujawnić zagnieżdżony typ, który implementuje indeksator. Wyobrażam sobie, że zacząłbyś widzieć wiele hackerów, próbując wbić coś w wiązania danych i właściwości, które powinny być metodami.
Josh
11
I miejmy nadzieję, że automatycznie zaimplementowana INotifyPropertyChanged jest znacznie wyżej na liście niż właściwości indeksowane. :)
Josh
3
@Eric, OK, tak podejrzewałem ... i tak dziękuję za odpowiedź! Chyba mogę żyć bez indeksowanych nieruchomości, tak jak robiłem to od lat;)
Thomas Levesque
5
@Martin: Nie jestem ekspertem od tego, jak duże zespoły programistyczne są określane przez budżety. Twoje pytanie powinno być skierowane do Somy, Jasona Zandera lub Scotta Wiltamutha. Myślę, że wszyscy od czasu do czasu piszą blogi. Twoje porównanie do Scali to porównanie jabłek do pomarańczy; Scala nie ma większości kosztów, które ponosi C #; żeby wymienić tylko jeden, nie ma milionów użytkowników z niezwykle ważnymi wymaganiami dotyczącymi kompatybilności wstecznej. Mógłbym wymienić wiele innych czynników, które mogą powodować ogromne różnice w kosztach między C # a Scalą.
Eric Lippert,
12
+1: Czytając to, ludzie mogą się wiele dowiedzieć o zarządzaniu projektami oprogramowania. Ma tylko kilka linijek.
Brian MacKay
22

Indeksator AC # to właściwość indeksowana. Jest on Itemdomyślnie nazwany (i możesz się do niego odwoływać np. Z VB) i możesz go zmienić za pomocą IndexerNameAttribute, jeśli chcesz.

Nie jestem pewien, dlaczego, konkretnie, został zaprojektowany w ten sposób, ale wydaje się, że jest to celowe ograniczenie. Jest to jednak zgodne z wytycznymi dotyczącymi projektowania struktury, które zalecają podejście nieindeksowanej właściwości zwracającej indeksowany obiekt dla kolekcji elementów członkowskich. To znaczy „możliwość indeksowania” jest cechą pewnego typu; jeśli jest indeksowalny na więcej niż jeden sposób, to naprawdę powinien zostać podzielony na kilka typów.

Pavel Minaev
źródło
1
Dziękuję Ci! Wielokrotnie walczyłem z błędami podczas implementowania interfejsów COM międzyoperacyjnych, które mają domyślny indxer (DISPID 0), który importuje jako ten [int], ale którego nazwa nie była pierwotnie "Item" (czasami jest to "item" lub "value" lub podobna ). To kompiluje się i działa mimo to, ale prowadzi do ostrzeżeń FxCop CA1033 InterfaceMethodsShouldBeCallableByChildTypes, problemów ze zgodnością z CLS (identyfikatory różniące się tylko wielkością liter) itp., Ponieważ nazwa nie do końca pasuje. [IndexerName] to wszystko, czego potrzebowałem, ale nigdy nie udało mi się go znaleźć.
puetzk
Dziękuję Ci!!! Atrybut IndexerName umożliwił mi dokończenie konwersji zestawu VB na C # bez przerywania podpisów MSIL.
Jon Tirjan
15

Ponieważ w pewnym sensie możesz to już zrobić i zmusza cię to do myślenia w aspektach OO, dodanie indeksowanych właściwości po prostu doda więcej szumu do języka. I po prostu inny sposób na zrobienie innej rzeczy.

class Foo
{
    public Values Values { ... }
}

class Values
{
    public string this[int index] { ... }    
}

foo.Values[0]

Osobiście wolałbym widzieć tylko jeden sposób zrobienia czegoś, a nie 10 sposobów. Ale oczywiście jest to subiektywna opinia.


źródło
2
+1, jest to o wiele lepszy sposób implementacji niż podgumowanie języka konstrukcjami VB5.
Josh
1
+1, bo tak bym to zrobił. A gdybyś był dobry, prawdopodobnie mógłbyś zrobić ten rodzaj.
Tony,
10
Jednym z problemów z takim podejściem jest to, że inny kod może wykonać kopię indeksatora i nie jest jasne, jaka powinna być semantyka, jeśli tak. Jeśli w kodzie jest napisane „var userList = Foo.Users; Foo.RemoveSomeUsers (); someUser = userList [5];” czy to powinno być elementem [5] Foo (przed RemoveSomeUsers), czy po? Gdyby jeden userList [] był indeksowaną właściwością, nie musiałby być bezpośrednio ujawniany.
supercat,
Czy lubisz przydzielać transjenty?
Joshua
9

Kiedyś faworyzowałem ideę indeksowanych właściwości, ale potem zdałem sobie sprawę, że dodałoby to okropną niejednoznaczność i faktycznie zniechęciłoby funkcjonalność. Indeksowane właściwości oznaczałyby, że nie masz instancji kolekcji podrzędnej. To jest dobre i złe. Wdrożenie jest mniej kłopotliwe i nie potrzebujesz odniesienia z powrotem do otaczającej klasy właściciela. Ale oznacza to również, że nie możesz przekazać tej podrzędnej kolekcji do niczego; prawdopodobnie będziesz musiał wyliczyć za każdym razem. Nie możesz też tego zrobić. Co najgorsze, patrząc na indeksowaną właściwość nie można stwierdzić, czy jest to ta właściwość, czy też właściwość kolekcji.

Pomysł jest racjonalny, ale prowadzi tylko do braku elastyczności i nagłej niezręczności.

Joshua A. Schaeffer
źródło
Ciekawe przemyślenia, nawet jeśli odpowiedź jest trochę spóźniona;). Zarabiasz bardzo dobre punkty, +1.
Thomas Levesque
W vb.net klasa może mieć zarówno indeksowaną, jak i nieindeksowaną właściwość o tej samej nazwie [np Bar.]. Wyrażenie Thing.Bar(5)użyje właściwości indeksowanej Baron Thing, podczas gdy (Thing.Bar)(5)użyje właściwości niezindeksowanej, Bara następnie użyje domyślnego indeksatora wynikowego obiektu. W moim przekonaniu pozwolenie Thing.Bar[5]na bycie własnością Thing, a nie własnością Thing.Bar, jest dobre, bo między innymi możliwe jest, że w pewnym momencie znaczenie Thing.Bar[4]może być jasne, ale właściwy efekt ...
supercat
... coś takiego var temp=Thing.Bar; do_stuff_with_thing; var q=temp[4]może być niejasne. Rozważ również koncepcję, która Thingmoże przechowywać dane Barw polu, które może być wspólnym niezmiennym obiektem lub niewspółdzielonym, zmiennym obiektem; próba zapisu, Bargdy pole zapasowe jest niezmienne, powinna spowodować utworzenie mutowalnej kopii, ale próba odczytu z niego nie powinna. Jeśli Barjest to nazwana właściwość indeksowana, indeksowany pobierający może pozostawić kolekcję zapasową w spokoju (bez względu na to, czy jest modyfikowalna, czy nie), podczas gdy metoda ustawiająca może w razie potrzeby utworzyć nową mutowalną kopię.
supercat
indeksator nieruchomości nie jest wyliczającym - to klucz
George Birbilis
6

Brak indeksowanych właściwości jest bardzo frustrujący, gdy próbuję napisać czysty, zwięzły kod. Właściwość indeksowana ma zupełnie inne konotacje niż dostarczanie odwołania do klasy, które jest indeksowane lub udostępnia indywidualne metody. Uważam za nieco niepokojące, że zapewnienie dostępu do wewnętrznego obiektu, który implementuje indeksowaną właściwość, jest nawet uważane za dopuszczalne, ponieważ często łamie jeden z kluczowych elementów orientacji obiektowej: hermetyzację.

Wystarczająco często napotykam ten problem, ale właśnie dziś napotkałem go ponownie, więc przedstawię przykład kodu z prawdziwego świata. Interfejs i tworzona klasa przechowuje konfigurację aplikacji, która jest zbiorem luźno powiązanych informacji. Musiałem dodać nazwane fragmenty skryptów, a użycie nienazwanego indeksatora klas spowodowałoby bardzo zły kontekst, ponieważ fragmenty skryptu są tylko częścią konfiguracji.

Gdyby indeksowane właściwości były dostępne w C #, mógłbym zaimplementować poniższy kod (składnia to [klucz] zmieniony na PropertyName [klucz]).

public interface IConfig
{
    // Other configuration properties removed for examp[le

    /// <summary>
    /// Script fragments
    /// </summary>
    string Scripts[string name] { get; set; }
}

/// <summary>
/// Class to handle loading and saving the application's configuration.
/// </summary>
internal class Config : IConfig, IXmlConfig
{
  #region Application Configuraiton Settings

    // Other configuration properties removed for examp[le

    /// <summary>
    /// Script fragments
    /// </summary>
    public string Scripts[string name]
    {
        get
        {
            if (!string.IsNullOrWhiteSpace(name))
            {
                string script;
                if (_scripts.TryGetValue(name.Trim().ToLower(), out script))
                    return script;
            }
            return string.Empty;
        }
        set
        {
            if (!string.IsNullOrWhiteSpace(name))
            {
                _scripts[name.Trim().ToLower()] = value;
                OnAppConfigChanged();
            }
        }
    }
    private readonly Dictionary<string, string> _scripts = new Dictionary<string, string>();

  #endregion

    /// <summary>
    /// Clears configuration settings, but does not clear internal configuration meta-data.
    /// </summary>
    private void ClearConfig()
    {
        // Other properties removed for example
        _scripts.Clear();
    }

  #region IXmlConfig

    void IXmlConfig.XmlSaveTo(int configVersion, XElement appElement)
    {
        Debug.Assert(configVersion == 2);
        Debug.Assert(appElement != null);

        // Saving of other properties removed for example

        if (_scripts.Count > 0)
        {
            var scripts = new XElement("Scripts");
            foreach (var kvp in _scripts)
            {
                var scriptElement = new XElement(kvp.Key, kvp.Value);
                scripts.Add(scriptElement);
            }
            appElement.Add(scripts);
        }
    }

    void IXmlConfig.XmlLoadFrom(int configVersion, XElement appElement)
    {
        // Implementation simplified for example

        Debug.Assert(appElement != null);
        ClearConfig();
        if (configVersion == 2)
        {
            // Loading of other configuration properites removed for example

            var scripts = appElement.Element("Scripts");
            if (scripts != null)
                foreach (var script in scripts.Elements())
                    _scripts[script.Name.ToString()] = script.Value;
        }
        else
            throw new ApplicaitonException("Unknown configuration file version " + configVersion);
    }

  #endregion
}

Niestety indeksowane właściwości nie są zaimplementowane, więc zaimplementowałem klasę do ich przechowywania i zapewniłem do tego dostęp. Jest to niepożądana implementacja, ponieważ celem klasy konfiguracji w tym modelu domeny jest hermetyzacja wszystkich szczegółów. Klienci tej klasy będą uzyskiwać dostęp do określonych fragmentów skryptów po nazwie i nie będą mieli powodu, aby je liczyć lub wyliczać.

Mogłem to zaimplementować jako:

public string ScriptGet(string name)
public void ScriptSet(string name, string value)

Co prawdopodobnie powinienem mieć, ale jest to przydatna ilustracja, dlaczego używanie klas indeksowanych jako zamienników tej brakującej funkcji często nie jest rozsądnym substytutem.

Aby zaimplementować podobne możliwości jako właściwość indeksowaną, musiałem napisać poniższy kod, który, jak zauważysz, jest znacznie dłuższy, bardziej złożony, a przez to trudniejszy do odczytania, zrozumienia i utrzymania.

public interface IConfig
{
    // Other configuration properties removed for examp[le

    /// <summary>
    /// Script fragments
    /// </summary>
    ScriptsCollection Scripts { get; }
}

/// <summary>
/// Class to handle loading and saving the application's configuration.
/// </summary>
internal class Config : IConfig, IXmlConfig
{
    public Config()
    {
        _scripts = new ScriptsCollection();
        _scripts.ScriptChanged += ScriptChanged;
    }

  #region Application Configuraiton Settings

    // Other configuration properties removed for examp[le

    /// <summary>
    /// Script fragments
    /// </summary>
    public ScriptsCollection Scripts
    { get { return _scripts; } }
    private readonly ScriptsCollection _scripts;

    private void ScriptChanged(object sender, ScriptChangedEventArgs e)
    {
        OnAppConfigChanged();
    }

  #endregion

    /// <summary>
    /// Clears configuration settings, but does not clear internal configuration meta-data.
    /// </summary>
    private void ClearConfig()
    {
        // Other properties removed for example
        _scripts.Clear();
    }

  #region IXmlConfig

    void IXmlConfig.XmlSaveTo(int configVersion, XElement appElement)
    {
        Debug.Assert(configVersion == 2);
        Debug.Assert(appElement != null);

        // Saving of other properties removed for example

        if (_scripts.Count > 0)
        {
            var scripts = new XElement("Scripts");
            foreach (var kvp in _scripts)
            {
                var scriptElement = new XElement(kvp.Key, kvp.Value);
                scripts.Add(scriptElement);
            }
            appElement.Add(scripts);
        }
    }

    void IXmlConfig.XmlLoadFrom(int configVersion, XElement appElement)
    {
        // Implementation simplified for example

        Debug.Assert(appElement != null);
        ClearConfig();
        if (configVersion == 2)
        {
            // Loading of other configuration properites removed for example

            var scripts = appElement.Element("Scripts");
            if (scripts != null)
                foreach (var script in scripts.Elements())
                    _scripts[script.Name.ToString()] = script.Value;
        }
        else
            throw new ApplicaitonException("Unknown configuration file version " + configVersion);
    }

  #endregion
}

public class ScriptsCollection : IEnumerable<KeyValuePair<string, string>>
{
    private readonly Dictionary<string, string> Scripts = new Dictionary<string, string>();

    public string this[string name]
    {
        get
        {
            if (!string.IsNullOrWhiteSpace(name))
            {
                string script;
                if (Scripts.TryGetValue(name.Trim().ToLower(), out script))
                    return script;
            }
            return string.Empty;
        }
        set
        {
            if (!string.IsNullOrWhiteSpace(name))
                Scripts[name.Trim().ToLower()] = value;
        }
    }

    public void Clear()
    {
        Scripts.Clear();
    }

    public int Count
    {
        get { return Scripts.Count; }
    }

    public event EventHandler<ScriptChangedEventArgs> ScriptChanged;

    protected void OnScriptChanged(string name)
    {
        if (ScriptChanged != null)
        {
            var script = this[name];
            ScriptChanged.Invoke(this, new ScriptChangedEventArgs(name, script));
        }
    }

  #region IEnumerable

    public IEnumerator<KeyValuePair<string, string>> GetEnumerator()
    {
        return Scripts.GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }

  #endregion
}

public class ScriptChangedEventArgs : EventArgs
{
    public string Name { get; set; }
    public string Script { get; set; }

    public ScriptChangedEventArgs(string name, string script)
    {
        Name = name;
        Script = script;
    }
}
James Higgins
źródło
2

Inne obejście jest wymienione w Łatwe tworzenie właściwości, które obsługują indeksowanie w C # , co wymaga mniej pracy.

EDYCJA : Powinienem również dodać, że w odpowiedzi na pierwotne pytanie, że jeśli potrafimy osiągnąć żądaną składnię, z obsługą biblioteki, to myślę, że potrzeba bardzo mocnych argumentów, aby dodać ją bezpośrednio do języka, aby zminimalizować wzdęcia językowe.

cdiggins
źródło
2
Odpowiadając na twoją edycję: nie sądzę, że spowodowałoby to nadęty język; składnia jest już dostępna dla indeksatorów klas ( this[]), po prostu muszą zezwolić na identyfikator zamiast this. Ale wątpię, czy kiedykolwiek zostanie uwzględniony w języku, z powodów wyjaśnionych przez Erica w swojej odpowiedzi
Thomas Levesque
1

Cóż, powiedziałbym, że go nie dodali, ponieważ nie było to warte kosztów projektowania, wdrażania, testowania i dokumentowania.

Żarty na bok, prawdopodobnie dlatego, że obejścia są proste, a funkcja nigdy nie skraca czasu w porównaniu z korzyściami. Nie zdziwiłbym się jednak, gdyby okazało się, że to zmiana w przyszłości.

Zapomniałeś również wspomnieć, że łatwiejszym obejściem jest po prostu wykonanie zwykłej metody:

public void SetFoo(int index, Foo toSet) {...}
public Foo GetFoo(int index) {...}
Ron Warholic
źródło
Bardzo prawdziwe. Jeśli składnia właściwości jest absolutnie niezbędna, możesz użyć obejścia Iona (być może z niektórymi rodzajami generycznymi, aby umożliwić różne typy zwracania). Niezależnie od tego, myślę, że mówi to o względnej łatwości wykonania tej samej pracy bez dodatkowych funkcji językowych.
Ron Warholic
1

Istnieje proste, ogólne rozwiązanie wykorzystujące lambdy do proxy funkcji indeksowania

Do indeksowania tylko do odczytu

public class RoIndexer<TIndex, TValue>
{
    private readonly Func<TIndex, TValue> _Fn;

    public RoIndexer(Func<TIndex, TValue> fn)
    {
        _Fn = fn;
    }

    public TValue this[TIndex i]
    {
        get
        {
            return _Fn(i);
        }
    }
}

Do indeksowania zmiennego

public class RwIndexer<TIndex, TValue>
{
    private readonly Func<TIndex, TValue> _Getter;
    private readonly Action<TIndex, TValue> _Setter;

    public RwIndexer(Func<TIndex, TValue> getter, Action<TIndex, TValue> setter)
    {
        _Getter = getter;
        _Setter = setter;
    }

    public TValue this[TIndex i]
    {
        get
        {
            return _Getter(i);
        }
        set
        {
            _Setter(i, value);
        }
    }
}

i fabrykę

public static class Indexer
{
    public static RwIndexer<TIndex, TValue> Create<TIndex, TValue>(Func<TIndex, TValue> getter, Action<TIndex, TValue> setter)
    {
        return new RwIndexer<TIndex, TValue>(getter, setter);
    } 
    public static RoIndexer<TIndex, TValue> Create<TIndex, TValue>(Func<TIndex, TValue> getter)
    {
        return new RoIndexer<TIndex, TValue>(getter);
    } 
}

we własnym kodzie używam go jak

public class MoineauFlankContours
{

    public MoineauFlankContour Rotor { get; private set; }

    public MoineauFlankContour Stator { get; private set; }

     public MoineauFlankContours()
    {
        _RoIndexer = Indexer.Create(( MoineauPartEnum p ) => 
            p == MoineauPartEnum.Rotor ? Rotor : Stator);
    }
    private RoIndexer<MoineauPartEnum, MoineauFlankContour> _RoIndexer;

    public RoIndexer<MoineauPartEnum, MoineauFlankContour> FlankFor
    {
        get
        {
            return _RoIndexer;
        }
    }

}

i za pomocą instancji MoineauFlankContours mogę to zrobić

MoineauFlankContour rotor = contours.FlankFor[MoineauPartEnum.Rotor];
MoineauFlankContour stator = contours.FlankFor[MoineauPartEnum.Stator];
bradgonesurfing
źródło
Sprytne, ale chyba lepiej byłoby buforować indeksator zamiast go tworzyć za każdym razem;)
Thomas Levesque