Warum implementiert C # keine indizierten Eigenschaften?

83

Ich weiß, ich weiß ... Eric Lipperts Antwort auf diese Art von Frage lautet normalerweise " weil es die Kosten für das Entwerfen, Implementieren, Testen und Dokumentieren nicht wert war ".

Trotzdem möchte ich eine bessere Erklärung ... Ich habe diesen Blog-Beitrag über neue C # 4-Funktionen gelesen und im Abschnitt über COM Interop hat der folgende Teil meine Aufmerksamkeit erregt:

Übrigens verwendet dieser Code eine weitere neue Funktion: indizierte Eigenschaften (sehen Sie sich diese eckigen Klammern nach Range genauer an.) Diese Funktion ist jedoch nur für COM-Interop verfügbar. Sie können in C # 4.0 keine eigenen indizierten Eigenschaften erstellen .

OK, aber warum ? Ich wusste bereits und bedauerte, dass es nicht möglich war, indizierte Eigenschaften in C # zu erstellen, aber dieser Satz ließ mich noch einmal darüber nachdenken. Ich sehe mehrere gute Gründe, es umzusetzen:

  • Die CLR unterstützt dies ( PropertyInfo.GetValuehat beispielsweise einen indexParameter), daher ist es schade, dass wir es in C # nicht nutzen können
  • Es wird für COM-Interop unterstützt, wie im Artikel gezeigt (unter Verwendung des dynamischen Versands).
  • Es ist in VB.NET implementiert
  • Es ist bereits möglich, Indexer zu erstellen, dh einen Index auf das Objekt selbst anzuwenden. Daher wäre es wahrscheinlich keine große Sache, die Idee auf Eigenschaften auszudehnen, dieselbe Syntax beizubehalten und nur durch thiseinen Eigenschaftsnamen zu ersetzen

Es würde erlauben, solche Dinge zu schreiben:

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

Derzeit besteht die einzige mir bekannte Problemumgehung darin, eine innere Klasse ( ValuesCollectionzum Beispiel) zu erstellen , die einen Indexer implementiert, und die ValuesEigenschaft so zu ändern , dass eine Instanz dieser inneren Klasse zurückgegeben wird.

Das ist sehr einfach, aber nervig ... Vielleicht könnte der Compiler es für uns tun! Eine Option wäre, eine innere Klasse zu generieren, die den Indexer implementiert, und ihn über eine öffentliche generische Schnittstelle verfügbar zu machen:

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

Aber in diesem Fall würde die Eigenschaft natürlich tatsächlich a zurückgeben IIndexer<int, string>und wäre nicht wirklich eine indizierte Eigenschaft ... Es wäre besser, eine echte CLR-indizierte Eigenschaft zu generieren.

Was denken Sie ? Möchten Sie diese Funktion in C # sehen? Wenn nicht, warum?

Thomas Levesque
quelle
1
Ich habe das Gefühl, dass dies ein weiteres Problem ist: "Wir erhalten Anfragen für X, aber nicht mehr als für Y" .
ChaosPandion
1
@ChaosPandion, ja, Sie haben wahrscheinlich Recht ... Aber diese Funktion wäre wahrscheinlich ziemlich einfach zu implementieren, und obwohl sie sicherlich kein "Muss" ist, fällt sie definitiv in die Kategorie "schön zu haben"
Thomas Levesque
4
Indexer sind aus CLR-Sicht schon etwas nervig. Sie fügen dem Code, der mit Eigenschaften arbeiten möchte, einen neuen Grenzfall hinzu, da jetzt jede Eigenschaft möglicherweise Indexerparameter haben kann. Ich denke, die C # -Implementierung ist sinnvoll, da das Konzept, das ein Indexer normalerweise darstellt, keine Eigenschaft eines Objekts ist, sondern dessen 'Inhalt'. Wenn Sie beliebige Indexereigenschaften angeben, implizieren Sie, dass die Klasse unterschiedliche Inhaltsgruppen haben kann, was natürlich dazu führt, dass der komplexe Unterinhalt als neue Klasse gekapselt wird. Meine Frage ist: Warum bietet die CLR indizierte Eigenschaften?
Dan Bryant
1
@tk_ danke für deinen konstruktiven Kommentar. Veröffentlichen Sie ähnliche Kommentare zu allen Posts über Sprachen, die nicht Free Pascal sind? Nun, ich hoffe, Sie fühlen sich gut dabei ...
Thomas Levesque
3
Dies ist eine der wenigen Situationen, in denen C ++ / CLI und VB.net besser sind als C #. Ich habe viele indizierte Eigenschaften in meinen C ++ / CLI-Code implementiert und jetzt, nachdem ich ihn in C # konvertiert habe, muss ich für alle Problemumgehungen finden. :-( SAUGT !!! // Dein Es würde erlauben, solche Dinge zu schreiben, was ich über Jahre getan habe.
Tobias Knauss

Antworten:

120

So haben wir C # 4 entworfen.

Zuerst haben wir eine Liste aller möglichen Funktionen erstellt, die wir der Sprache hinzufügen könnten.

Dann haben wir die Funktionen in "Das ist schlecht, wir dürfen es niemals tun", "Das ist großartig, wir müssen es tun" und "Das ist gut, aber diesmal wollen wir es nicht tun" zusammengefasst.

Dann haben wir uns angesehen, wie viel Budget wir für das Entwerfen, Implementieren, Testen, Dokumentieren, Versenden und Warten der "Muss" -Funktionen benötigen, und festgestellt, dass wir 100% über dem Budget liegen.

Also haben wir ein paar Sachen aus dem "Muss" -Eimer in den "Schön zu haben" -Eimer verschoben.

Indizierte Eigenschaften waren nie irgendwo in der Nähe der Spitze der Liste „Muss ich haben“. Sie sind sehr niedrig auf der "netten" Liste und flirten mit der "schlechten Idee" Liste.

Jede Minute, die wir mit dem Entwerfen, Implementieren, Testen, Dokumentieren oder Verwalten von netten Funktionen X verbringen, ist eine Minute, die wir nicht mit fantastischen Funktionen A, B, C, D, E, F und G verbringen können. Wir müssen rücksichtslos Prioritäten setzen, damit wir nur die bestmöglichen Funktionen ausführen. Indizierte Eigenschaften wären nett, aber nett ist nicht annähernd gut genug, um tatsächlich implementiert zu werden.

Eric Lippert
quelle
20
Kann ich eine Stimme hinzufügen, um sie auf die schlechte Liste zu setzen? Ich sehe nicht wirklich, dass die aktuelle Implementierung eine große Einschränkung darstellt, wenn Sie nur einen verschachtelten Typ verfügbar machen könnten, der einen Indexer implementiert. Ich würde mir vorstellen, dass Sie eine Menge Hackery sehen würden, um zu versuchen, etwas in Datenbindung und Eigenschaften zu stecken, die Methoden sein sollten.
Josh
11
Und hoffentlich ist INotifyPropertyChanged, das automatisch implementiert wird, weit höher in der Liste als indizierte Eigenschaften. :)
Josh
3
@ Eric, OK, das habe ich vermutet ... danke, dass du trotzdem geantwortet hast! Ich denke, ich kann ohne indizierte Immobilien leben, wie ich es seit Jahren getan habe;)
Thomas Levesque
5
@ Martin: Ich bin kein Experte dafür, wie große Softwareteams Budgets bestimmen. Ihre Frage sollte an Soma, Jason Zander oder Scott Wiltamuth gerichtet werden. Ich glaube, alle schreiben gelegentlich Blogs. Ihr Vergleich mit Scala ist ein Vergleich zwischen Äpfeln und Orangen. Scala hat nicht die meisten Kosten, die C # verursacht; Um nur einen zu nennen: Es gibt nicht Millionen von Benutzern mit äußerst wichtigen Anforderungen an die Abwärtskompatibilität. Ich könnte Ihnen noch viele weitere Faktoren nennen, die zu massiven Kostenunterschieden zwischen C # und Scala führen könnten.
Eric Lippert
12
+1: Die Leute könnten viel über das Verwalten von Softwareprojekten lernen, wenn sie dies lesen. Und es ist nur ein paar Zeilen lang.
Brian MacKay
22

Der AC # -Indexer ist eine indizierte Eigenschaft. Es ist Itemstandardmäßig benannt (und Sie können es beispielsweise von VB als solches bezeichnen), und Sie können es mit IndexerNameAttribute ändern, wenn Sie möchten.

Ich bin mir nicht sicher, warum es speziell so entworfen wurde, aber es scheint eine absichtliche Einschränkung zu sein. Es steht jedoch im Einklang mit den Framework Design Guidelines, die den Ansatz einer nicht indizierten Eigenschaft empfehlen, die ein indizierbares Objekt für Mitgliedssammlungen zurückgibt. Dh "indexierbar sein" ist ein Merkmal eines Typs; Wenn es auf mehrere Arten indizierbar ist, sollte es wirklich in mehrere Typen aufgeteilt werden.

Pavel Minaev
quelle
1
Danke dir! Ich habe wiederholt mit Fehlern gekämpft, als ich COM-Interop-Schnittstellen implementiert habe, die einen Standard-Indxer (DISPID 0) haben, der als dieser [int] importiert wird, dessen Name jedoch ursprünglich nicht "Item" war (manchmal ist es "item" oder "value" oder ähnlich ). Dies wird trotzdem kompiliert und ausgeführt, führt jedoch zu FxCop CA1033 InterfaceMethodsShouldBeCallableByChildTypes-Warnungen, CLS-Konformitätsproblemen (Bezeichner unterscheiden sich nur für den Fall) usw., da der Name nicht ganz passt. [IndexerName] ist alles, was benötigt wurde, aber ich hatte es nie geschafft, es zu finden.
Puetzk
Danke dir!!! Mit dem IndexerName-Attribut konnte ich die Konvertierung einer VB-Assembly in C # abschließen, ohne die MSIL-Signaturen zu beschädigen.
Jon Tirjan
15

Da Sie dies bereits tun können und gezwungen sind, über OO-Aspekte nachzudenken, würde das Hinzufügen indizierter Eigenschaften der Sprache nur mehr Rauschen hinzufügen. Und nur eine andere Möglichkeit, etwas anderes zu tun.

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

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

foo.Values[0]

Ich persönlich würde es vorziehen, nur einen einzigen Weg zu sehen, etwas zu tun, anstatt 10 Wege. Aber das ist natürlich eine subjektive Meinung.


quelle
2
+1, dies ist eine viel bessere Möglichkeit, es zu implementieren, als die Sprache mit VB5-Konstrukten zu verfeinern.
Josh
1
+1, weil ich es so machen würde. Und wenn Sie gut wären, könnten Sie dieses Generikum wahrscheinlich machen.
Tony
10
Ein Problem bei diesem Ansatz besteht darin, dass anderer Code eine Kopie des Indexers erstellen kann und es unklar ist, wie die Semantik aussehen sollte, wenn dies der Fall ist. Wenn der Code "var userList = Foo.Users; Foo.RemoveSomeUsers (); someUser = userList [5];" sollte das das sein, was früher Element [5] von Foo war (vor den RemoveSomeUsers) oder danach? Wenn eine userList [] eine indizierte Eigenschaft wäre, müsste sie nicht direkt verfügbar gemacht werden.
Supercat
Ordnen Sie gerne Transienten zu?
Joshua
9

Früher habe ich die Idee der indizierten Eigenschaften favorisiert, aber dann wurde mir klar, dass dies schreckliche Mehrdeutigkeiten hinzufügen und die Funktionalität tatsächlich beeinträchtigen würde . Indizierte Eigenschaften würden bedeuten, dass Sie keine untergeordnete Sammlungsinstanz haben. Das ist sowohl gut als auch schlecht. Die Implementierung ist weniger schwierig und Sie benötigen keinen Verweis auf die einschließende Eigentümerklasse. Es bedeutet aber auch, dass Sie diese untergeordnete Sammlung an nichts weitergeben können. Sie müssten wahrscheinlich jedes Mal aufzählen. Sie können es auch nicht tun. Am schlimmsten ist, dass Sie anhand einer indizierten Eigenschaft nicht erkennen können, ob es sich um diese oder eine Sammlungseigenschaft handelt.

Die Idee ist rational, führt aber nur zu Inflexibilität und abrupter Unbeholfenheit.

Joshua A. Schaeffer
quelle
Interessante Gedanken, auch wenn die Antwort etwas spät ist;). Du machst sehr gute Punkte, +1.
Thomas Levesque
In vb.net kann eine Klasse sowohl eine indizierte als auch eine nicht indizierte Eigenschaft mit demselben Namen haben [z Bar. B. ]. Der Ausdruck Thing.Bar(5)würde die indizierte Eigenschaft Barfür verwenden Thing, während er (Thing.Bar)(5)die nicht indizierte Eigenschaft verwenden Barund dann den Standardindexer des resultierenden Objekts verwenden würde. Für mich ist es Thing.Bar[5]gut , zuzulassen, dass es eine Eigenschaft von Thingund keine Eigenschaft von Thing.Barist, da es unter anderem möglich ist, dass zu einem bestimmten Zeitpunkt die Bedeutung von Thing.Bar[4]klar ist, aber die richtige Wirkung von ...
Supercat
... so etwas var temp=Thing.Bar; do_stuff_with_thing; var q=temp[4]mag unklar sein. Betrachten Sie auch den Begriff, Thingder die Daten Barin einem Feld enthalten könnte, das entweder ein gemeinsames unveränderliches Objekt oder ein nicht gemeinsam genutztes veränderbares Objekt sein könnte. Ein Versuch, zu schreiben, Barwenn das Hintergrundfeld unveränderlich ist, sollte eine veränderbare Kopie erstellen, ein Versuch, daraus zu lesen, jedoch nicht. Wenn Bares sich um eine benannte indizierte Eigenschaft handelt, kann der indizierte Getter die Hintergrundsammlung in Ruhe lassen (ob veränderbar oder nicht), während der Setter bei Bedarf eine neue veränderbare Kopie erstellen kann.
Supercat
Ein Indexer einer Eigenschaft ist kein Enumerator - es ist ein Schlüssel
George Birbilis
6

Ich finde das Fehlen indizierter Eigenschaften sehr frustrierend, wenn ich versuche, sauberen, präzisen Code zu schreiben. Eine indizierte Eigenschaft hat eine ganz andere Konnotation als die Bereitstellung einer indizierten Klassenreferenz oder die Bereitstellung einzelner Methoden. Ich finde es etwas beunruhigend, dass die Bereitstellung des Zugriffs auf ein internes Objekt, das eine indizierte Eigenschaft implementiert, sogar als akzeptabel angesehen wird, da dies häufig eine der Schlüsselkomponenten der Objektorientierung verletzt: die Kapselung.

Ich bin oft genug auf dieses Problem gestoßen, aber ich bin es heute gerade wieder aufgetreten, daher werde ich ein reales Codebeispiel bereitstellen. Die Schnittstelle und Klasse, die geschrieben wird, speichert die Anwendungskonfiguration, bei der es sich um eine Sammlung lose verwandter Informationen handelt. Ich musste benannte Skriptfragmente hinzufügen und die Verwendung des unbenannten Klassenindexers hätte einen sehr falschen Kontext impliziert, da Skriptfragmente nur ein Teil der Konfiguration sind.

Wenn indizierte Eigenschaften in C # verfügbar wären, hätte ich den folgenden Code implementieren können (Syntax ist, dass dieser [Schlüssel] in PropertyName [Schlüssel] geändert wurde).

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
}

Leider sind indizierte Eigenschaften nicht implementiert, daher habe ich eine Klasse zum Speichern implementiert und Zugriff darauf gewährt. Dies ist eine unerwünschte Implementierung, da der Zweck der Konfigurationsklasse in diesem Domänenmodell darin besteht, alle Details zu kapseln. Clients dieser Klasse greifen namentlich auf bestimmte Skriptfragmente zu und haben keinen Grund, diese zu zählen oder aufzuzählen.

Ich hätte dies umsetzen können als:

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

Was ich wahrscheinlich haben sollte, aber dies ist ein nützliches Beispiel dafür, warum die Verwendung von indizierten Klassen als Ersatz für diese fehlende Funktion oft kein vernünftiger Ersatz ist.

Um eine ähnliche Funktion wie eine indizierte Eigenschaft zu implementieren, musste ich den folgenden Code schreiben, der erheblich länger, komplexer und daher schwerer zu lesen, zu verstehen und zu warten ist.

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
quelle
2

Eine weitere Problemumgehung finden Sie unter Einfache Erstellung von Eigenschaften, die die Indizierung in C # unterstützen und weniger Arbeit erfordern.

EDIT : Ich sollte auch hinzufügen, dass als Antwort auf die ursprüngliche Frage, dass meine, wenn wir die gewünschte Syntax mit Bibliotheksunterstützung erreichen können, es einen sehr starken Fall geben muss, um sie direkt zur Sprache hinzuzufügen, um dies zu tun Minimieren Sie das Aufblähen der Sprache.

cdiggins
quelle
2
Als Antwort auf Ihre Bearbeitung: Ich glaube nicht, dass dies zu einem Aufblähen der Sprache führen würde. Die Syntax ist bereits für Klassenindexer ( this[]) vorhanden. Sie müssen lediglich einen Bezeichner anstelle von zulassen this. Aber ich bezweifle, dass es jemals in die Sprache aufgenommen wird, aus den Gründen, die Eric in seiner Antwort erklärt hat
Thomas Levesque
1

Nun, ich würde sagen, dass sie es nicht hinzugefügt haben, weil es die Kosten für das Entwerfen, Implementieren, Testen und Dokumentieren nicht wert war.

Scherz beiseite, es liegt wahrscheinlich daran, dass die Problemumgehungen einfach sind und die Funktion niemals die Zeit im Vergleich zum Nutzen verkürzt. Es würde mich nicht wundern, wenn dies auf der ganzen Linie als Veränderung erscheint.

Sie haben auch vergessen zu erwähnen, dass eine einfachere Problemumgehung nur eine reguläre Methode ist:

public void SetFoo(int index, Foo toSet) {...}
public Foo GetFoo(int index) {...}
Ron Warholic
quelle
Sehr richtig. Wenn die Eigenschaftssyntax absolut wichtig ist, können Sie die Problemumgehung von Ion verwenden (möglicherweise mit einigen Generika, um eine Vielzahl von Rückgabetypen zu ermöglichen). Unabhängig davon denke ich, dass dies für die relative Leichtigkeit spricht, den gleichen Job ohne zusätzliche Sprachfunktionen zu erledigen.
Ron Warholic
1

Es gibt eine einfache allgemeine Lösung, bei der Lambdas zum Proxy der Indizierungsfunktion verwendet werden

Für schreibgeschützte Indizierung

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

Zur veränderlichen Indizierung

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

und eine Fabrik

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

in meinem eigenen Code benutze ich es gerne

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

}

und mit einer Instanz von MoineauFlankContours kann ich tun

MoineauFlankContour rotor = contours.FlankFor[MoineauPartEnum.Rotor];
MoineauFlankContour stator = contours.FlankFor[MoineauPartEnum.Stator];
Bradgonesurfing
quelle
Clever, aber es wäre wahrscheinlich besser, den Indexer zwischenzuspeichern, als ihn jedes Mal zu erstellen;)
Thomas Levesque
0

Ich habe auch selbst herausgefunden, dass Sie explizit implementierte Schnittstellen verwenden können, um dies zu erreichen, wie hier gezeigt: Benannte indizierte Eigenschaft in C #? (Siehe den zweiten Weg in dieser Antwort)

George Birbilis
quelle