Hat C # Erweiterungseigenschaften?

769

Hat C # Erweiterungseigenschaften?

Zum Beispiel kann ich eine Erweiterung Eigenschaft hinzufügen DateTimeFormatInfogenannt , ShortDateLongTimeFormatdie zurückkommen würde ShortDatePattern + " " + LongTimePattern?

Svish
quelle
14
Ich wollte eine Erweiterungsmethode namens IsNull auf Nullable <T> hinzufügen, die einfach zurückkehren würde! HasValue. .IsNull () ist definitiv weniger hübsch als .IsNull
Ken
1
Ich finde dies nützlich für Trinary Operator?
PedroC88
2
Ich wollte, dass dies Javas enumnachahmt, die Eigenschaften und Methoden haben können. C # enumkönnen keine Eigenschaften oder Methoden haben, aber Sie können Erweiterungsmethoden für sie erstellen. Diese Frage war nützlich für mich und sollte nicht geschlossen werden.
Ian McLaird
Obwohl, wie viele Leute gesagt haben, derzeit keine Pläne bestehen, dies der Sprache hinzuzufügen, gibt es keinen Grund, warum dies nicht möglich ist. Die Tatsache, dass F # nicht nur Erweiterungseigenschaften, sondern auch statische Erweiterungen hat, beweist für mich, dass dies zumindest eine gute Idee ist.
Richiban
2
Es sollte eine gemacht werden
Rootel

Antworten:

366

Im Moment wird es vom Roslyn-Compiler noch nicht sofort unterstützt ...

Bisher wurden die Erweiterungseigenschaften nicht als wertvoll genug angesehen, um in früheren Versionen des C # -Standards enthalten zu sein. C # 7 und C # 8.0 haben dies als Vorschlagschampion gesehen, aber es wurde noch nicht veröffentlicht, vor allem, weil sie es von Anfang an schaffen wollen, auch wenn es bereits eine Implementierung gibt.

Aber es wird ...

Es gibt ein Verlängerungselement Element in der C # 7 Arbeitsliste , so dass es in naher Zukunft unterstützt werden. Der aktuelle Status der Erweiterungseigenschaft finden Sie auf Github unter dem zugehörigen Element .

Es gibt jedoch ein noch vielversprechenderes Thema, nämlich "Alles erweitern", wobei der Schwerpunkt auf Eigenschaften und statischen Klassen oder sogar Feldern liegt.

Darüber hinaus können Sie eine Problemumgehung verwenden

Wie in diesem Artikel angegeben , können Sie die TypeDescriptorFunktion verwenden, um zur Laufzeit ein Attribut an eine Objektinstanz anzuhängen. Es wird jedoch nicht die Syntax der Standardeigenschaften verwendet.
Es unterscheidet sich ein wenig von syntaktischem Zucker und bietet die Möglichkeit, eine erweiterte Eigenschaft wie
string Data(this MyClass instance)einen Alias ​​für die Erweiterungsmethode zu definieren ,
string GetData(this MyClass instance)wenn Daten in der Klasse gespeichert werden.

Ich hoffe, dass C # 7 alles (Eigenschaften und Felder) mit allen Funktionen bietet, aber in diesem Punkt wird es nur die Zeit zeigen.

Und zögern Sie nicht, einen Beitrag zu leisten, da die Software von morgen von der Community kommt.

Update: August 2016

Als Dotnet-Team veröffentlichte, was in C # 7.0 neu ist und aus einem Kommentar von Mads Torgensen :

Erweiterungseigenschaften: Wir hatten einen (brillanten!) Praktikanten, der sie im Sommer als Experiment zusammen mit anderen Arten von Erweiterungsmitgliedern implementierte. Wir bleiben daran interessiert, aber es ist eine große Veränderung und wir müssen zuversichtlich sein, dass es sich lohnt.

Es scheint, dass Erweiterungseigenschaften und andere Mitglieder immer noch gute Kandidaten für eine zukünftige Version von Roslyn sind, aber möglicherweise nicht die Version 7.0.

Update: Mai 2017

Die Erweiterungsmitglieder wurden als Duplikat der Erweiterung geschlossen. Alles, was ebenfalls geschlossen ist. Die Hauptdiskussion befasste sich in der Tat mit der Erweiterbarkeit von Typen im weiteren Sinne. Die Funktion wird jetzt hier als Vorschlag verfolgt und aus dem 7.0-Meilenstein entfernt .

Update: August 2017 - Vorgeschlagene Funktion für C # 8.0

Während es immer noch nur ein vorgeschlagenes Feature bleibt , haben wir jetzt eine klarere Sicht auf die Syntax. Beachten Sie, dass dies auch die neue Syntax für Erweiterungsmethoden sein wird:

public interface IEmployee 
{
    public decimal Salary { get; set; }
}

public class Employee
{
    public decimal Salary { get; set; }
}

public extension MyPersonExtension extends Person : IEmployee
{
    private static readonly ConditionalWeakTable<Person, Employee> _employees = 
        new ConditionalWeakTable<Person, Employee>();


    public decimal Salary
    {
        get 
        {
            // `this` is the instance of Person
            return _employees.GetOrCreate(this).Salary; 
        }
        set 
        {
            Employee employee = null;
            if (!_employees.TryGetValue(this, out employee)
            {
                employee = _employees.GetOrCreate(this);
            }
            employee.Salary = value;
        }
    }
}

IEmployee person = new Person();
var salary = person.Salary;

Ähnlich wie Teilklassen, jedoch als separate Klasse / Typ in einer anderen Assembly kompiliert. Beachten Sie, dass Sie auf diese Weise auch statische Elemente und Operatoren hinzufügen können. Wie im Podcast von Mads Torgensen erwähnt , hat die Erweiterung keinen Status (daher können der Klasse keine privaten Instanzmitglieder hinzugefügt werden), was bedeutet, dass Sie keine mit der Instanz verknüpften privaten Instanzdaten hinzufügen können . Der Grund dafür ist, dass interne Wörterbücher verwaltet werden müssen und dies schwierig sein kann (Speicherverwaltung usw.). Dazu können Sie weiterhin die zuvor beschriebene TypeDescriptor/ ConditionalWeakTable-Technik verwenden und sie mit der Eigenschaftserweiterung unter einer schönen Eigenschaft ausblenden.

Die Syntax kann sich weiterhin ändern, da dies dieses Problem impliziert . Zum Beispiel extendskönnte ersetzt werden, durch fordie sich einige natürlicher und weniger mit Java verbunden fühlen.

Update Dezember 2018 - Rollen, Erweiterungen und statische Schnittstellenmitglieder

Die Erweiterung hat es nicht auf C # 8.0 geschafft, da einige Nachteile als Ende dieses GitHub-Tickets erklärt wurden . Es gab also eine Untersuchung, um das Design zu verbessern. Hier erklärt Mads Torgensen, was Rollen und Erweiterungen sind und wie sie sich unterscheiden:

Mithilfe von Rollen können Schnittstellen für bestimmte Werte eines bestimmten Typs implementiert werden. Mit Erweiterungen können Schnittstellen für alle Werte eines bestimmten Typs innerhalb eines bestimmten Codebereichs implementiert werden.

Es kann bei einer Aufteilung des vorherigen Vorschlags in zwei Anwendungsfälle gesehen werden. Die neue Syntax für die Erweiterung würde folgendermaßen aussehen:

public extension ULongEnumerable of ulong
{
    public IEnumerator<byte> GetEnumerator()
    {
        for (int i = sizeof(ulong); i > 0; i--)
        {
            yield return unchecked((byte)(this >> (i-1)*8));
        }
    }
}

dann könnten Sie dies tun:

foreach (byte b in 0x_3A_9E_F1_C5_DA_F7_30_16ul)
{
    WriteLine($"{e.Current:X}");
}

Und für eine statische Schnittstelle :

public interface IMonoid<T> where T : IMonoid<T>
{
    static T operator +(T t1, T t2);
    static T Zero { get; }
}

Fügen Sie eine Erweiterungseigenschaft auf intund behandelt die intals IMonoid<int>:

public extension IntMonoid of int : IMonoid<int>
{
    public static int Zero => 0;
}
Fab
quelle
58
Dies ist eine der nützlichsten Antworten, die ich je auf StackExchange verfolgt habe. Ständige Aktualisierung des Status und Aktualisierung aller, die darauf zurückkommen, und Bereitstellung solider Links zu Diskussion und Geschichte.
Bdrelling
25
Es ist großartig, dass Sie dies auf dem neuesten Stand halten - danke
David Thielen
1
Leider sind ab diesem Kommentar Rollen, Erweiterungen und statische Schnittstellenmitglieder nur für C # 11 gekennzeichnet :(
Ian Kemp
436

Nein, sie sind in C # 3.0 nicht vorhanden und werden in 4.0 nicht hinzugefügt. Es befindet sich auf der Liste der gewünschten Funktionen für C #, sodass es zu einem späteren Zeitpunkt hinzugefügt werden kann.

Zu diesem Zeitpunkt können Sie am besten Erweiterungsmethoden im GetXXX-Stil verwenden.

JaredPar
quelle
3
Ähnliches gilt für generische Eigenschaften: Sie müssen die Syntax 'GetXXX <>' verwenden.
Jay Bazuzi
3
ok, das habe ich mir gedacht @ Jay, ja, das hasse ich auch, hehe. Vor allem die Unfähigkeit , eine generische Indexer haben ... seufzen
Svish
75
Link zur Liste der gewünschten Funktionen?
Dan Esparza
2
Was ist mit Version 6.0 und 7.0?
Falk
2
Gibt es ab 2020 Updates dazu?
Chad
265

Nein, sie existieren nicht.

Ich weiß, dass das C # -Team sie zu einem bestimmten Zeitpunkt in Betracht gezogen hat (oder zumindest Eric Lippert) - zusammen mit Erweiterungskonstruktoren und -betreibern (es kann eine Weile dauern, bis Sie sich zurechtfinden, aber sie sind cool ...) Ich habe keine Beweise dafür gesehen, dass sie Teil von C # 4 sein werden.


EDIT: Sie sind nicht in C # 5 erschienen und ab Juli 2014 sieht es auch nicht so aus, als würde es in C # 6 sein.

Eric Lippert , der Hauptentwickler im C # -Compilerteam von Microsoft bis November 2012, hat im Oktober 2009 darüber gebloggt:

Jon Skeet
quelle
2
Ja, und sie könnten das Feld immer noch ausblenden. Wenn Sie eine einzelne Eigenschaft festlegen, werden möglicherweise zwei Eigenschaften darunter festgelegt, oder umgekehrt. (Stellen Sie sich etwas mit einer normalen Size-Eigenschaft und Width / Height-Erweiterungseigenschaften vor oder umgekehrt.) Ich vermute, sie wären jedoch nützlicher als schreibgeschützte.
Jon Skeet
23
Sie können sich nicht an Erweiterungsmethoden binden. In vielen Situationen kann es hilfreich sein, eigene Eigenschaften für die Datenbindung hinzuzufügen.
Nick
3
@leppie - Der Wert von Eigenschaftserweiterungen würde den Eigenschaften von Bool und String am meisten zugute kommen, denke ich. Das ()am Ende loszuwerden ist viel besser lesbar. Ich weiß für mich persönlich, dass mindestens 90% der Erweiterungen, die ich schreibe, von diesen beiden Typen sind.
Code Maverick
4
Um ein Beispiel zu geben, warum dies nützlich wäre, habe ich ein EFCF-Modell. In einigen Klassen habe ich schreibgeschützte Eigenschaften, mit denen ich formatierte Informationen zurückgeben kann: FullName= FirstName + LastName, ShortName= FirstName + LastName[0]. Ich möchte weitere dieser Eigenschaften hinzufügen, möchte aber die tatsächlichen Klassen nicht "verschmutzen". In diesem Fall ist eine schreibgeschützte Erweiterungseigenschaft perfekt, da ich die Funktionalität hinzufügen, die Hauptklasse sauber halten und dennoch die Informationen verfügbar machen kann, die ich in der Benutzeroberfläche verfügbar machen möchte.
Gup3rSuR4c
4
@JonSkeet: Sie haben Recht, am Ende habe ich getan, was ich wollte, indem ich meine eigene Klasse erstellt, dann alle relevanten Methoden und Eigenschaften für versiegelte Klassen verpackt und dann angegeben habe, static implicit operator FileInfo(FileInfoEx fex)welches mein enthaltenes FileInfo-Objekt zurückgibt. Auf diese Weise kann ich FileInfoEx effektiv so behandeln, als würde es von FileInfo erben, obwohl diese Klasse versiegelt ist.
Steve L
27

Update (danke an @chaost für den Hinweis auf dieses Update):

Mads Torgersen: "Erweiterung alles hat es nicht in C # 8.0 geschafft. Es wurde, wenn Sie so wollen, in einer sehr spannenden Debatte über die weitere Zukunft der Sprache" eingeholt ", und jetzt wollen wir sicherstellen, dass wir es nicht tun füge es so hinzu, dass diese zukünftigen Möglichkeiten gehemmt werden. Manchmal ist Sprachdesign ein sehr langes Spiel! "

Quelle: Kommentarbereich in https://blogs.msdn.microsoft.com/dotnet/2018/11/12/building-c-8-0/


Ich habe aufgehört zu zählen, wie oft ich diese Frage im Laufe der Jahre geöffnet habe, in der Hoffnung, dass dies umgesetzt wurde.

Nun, endlich können wir uns alle freuen! Microsoft wird dies in der kommenden C # 8-Version vorstellen.

Also anstatt dies zu tun ...

public static class IntExtensions
{
   public static bool Even(this int value)
   {
        return value % 2 == 0;
   }
}

Wir werden es endlich so machen können ...

public extension IntExtension extends int
{
    public bool Even => this % 2 == 0;
}

Quelle: https://blog.ndepend.com/c-8-0-features-glimpse-future/

Korayem
quelle
3
Diese Woche wurden C # 8.0-Funktionen angekündigt und ich habe leider nichts davon gesehen.
Mateo Torres-Ruiz
1
@ MateoTorres-Ruiz Ein Kommentar von 'Mads Torgersen' (C # dev), der auf jemanden antwortete, der danach fragte (vor 3 Tagen): "Die Erweiterung hat es nicht in C # 8.0 geschafft. Es wurde" eingeholt ", wenn Sie so wollen in einer sehr spannenden Debatte über die weitere Zukunft der Sprache, und jetzt wollen wir sicherstellen, dass wir sie nicht so hinzufügen, dass diese zukünftigen Möglichkeiten behindert werden. Manchmal ist Sprachdesign ein sehr langes Spiel! " Fühlt sich schlecht an .. (Lesen Sie dies auf Korayems Link, im Kommentarbereich)
Chaost
8

Wie bei @Psyonity erwähnt, können Sie mit der conditionalWeakTable vorhandenen Objekten Eigenschaften hinzufügen. In Kombination mit dem dynamischen ExpandoObject können Sie dynamische Erweiterungseigenschaften in wenigen Zeilen implementieren:

using System.Dynamic;
using System.Runtime.CompilerServices;

namespace ExtensionProperties
{
    /// <summary>
    /// Dynamically associates properies to a random object instance
    /// </summary>
    /// <example>
    /// var jan = new Person("Jan");
    ///
    /// jan.Age = 24; // regular property of the person object;
    /// jan.DynamicProperties().NumberOfDrinkingBuddies = 27; // not originally scoped to the person object;
    ///
    /// if (jan.Age &lt; jan.DynamicProperties().NumberOfDrinkingBuddies)
    /// Console.WriteLine("Jan drinks too much");
    /// </example>
    /// <remarks>
    /// If you get 'Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo.Create' you should reference Microsoft.CSharp
    /// </remarks>
    public static class ObjectExtensions
    {
        ///<summary>Stores extended data for objects</summary>
        private static ConditionalWeakTable<object, object> extendedData = new ConditionalWeakTable<object, object>();

        /// <summary>
        /// Gets a dynamic collection of properties associated with an object instance,
        /// with a lifetime scoped to the lifetime of the object
        /// </summary>
        /// <param name="obj">The object the properties are associated with</param>
        /// <returns>A dynamic collection of properties associated with an object instance.</returns>
        public static dynamic DynamicProperties(this object obj) => extendedData.GetValue(obj, _ => new ExpandoObject());
    }
}

Ein Verwendungsbeispiel finden Sie in den XML-Kommentaren:

var jan = new Person("Jan");

jan.Age = 24; // regular property of the person object;
jan.DynamicProperties().NumberOfDrinkingBuddies = 27; // not originally scoped to the person object;

if (jan.Age < jan.DynamicProperties().NumberOfDrinkingBuddies)
{
    Console.WriteLine("Jan drinks too much");
}

jan = null; // NumberOfDrinkingBuddies will also be erased during garbage collection
Realbart
quelle
Die beste Antwort
N73k
1

Da ich dies kürzlich brauchte, habe ich mir die Quelle der Antwort in folgenden Abschnitten angesehen:

c # Klasse durch Hinzufügen von Eigenschaften erweitern

und eine dynamischere Version erstellt:

public static class ObjectExtenders
{
    static readonly ConditionalWeakTable<object, List<stringObject>> Flags = new ConditionalWeakTable<object, List<stringObject>>();

    public static string GetFlags(this object objectItem, string key)
    {
        return Flags.GetOrCreateValue(objectItem).Single(x => x.Key == key).Value;
    }

    public static void SetFlags(this object objectItem, string key, string value)
    {
        if (Flags.GetOrCreateValue(objectItem).Any(x => x.Key == key))
        {
            Flags.GetOrCreateValue(objectItem).Single(x => x.Key == key).Value = value;
        }
        else
        {
            Flags.GetOrCreateValue(objectItem).Add(new stringObject()
            {
                Key = key,
                Value = value
            });
        }
    }

    class stringObject
    {
        public string Key;
        public string Value;
    }
}

Es kann wahrscheinlich stark verbessert werden (Benennung, dynamisch anstelle von Zeichenfolge). Ich verwende dies derzeit in CF 3.5 zusammen mit einer hackigen ConditionalWeakTable ( https://gist.github.com/Jan-WillemdeBruyn/db79dd6fdef7b9845e217958db98c4d4 ).

Psyonität
quelle
Entschuldigung, aber obwohl dies sehr gründlich aussieht, hat es nichts mit Erweiterungseigenschaften zu tun, sondern zeigt nur Erweiterungsmethoden.
Viking