C # ha proprietà di estensione?


769

C # ha proprietà di estensione?

Ad esempio, posso aggiungere una proprietà di estensione a DateTimeFormatInfochiamata ShortDateLongTimeFormatche restituirà ShortDatePattern + " " + LongTimePattern?


14
Volevo aggiungere un metodo di estensione chiamato IsNull su Nullable <T> che sarebbe appena tornato! HasValue. .IsNull () è decisamente meno carino di .IsNull
Ken

1
Lo trovo utile per l'operatore ?
trinario

2
Volevo che questo imitasse quelli di Java enumche possono avere proprietà e metodi. C # enumnon può avere proprietà o metodi, ma è possibile creare metodi di estensione su di essi. Questa domanda mi è stata utile e non dovrebbe essere chiusa.
Ian McLaird,

Sebbene, come molte persone hanno detto, al momento non ci siano piani per aggiungere questo alla lingua, non c'è motivo per cui non possa essere fatto. Il fatto che F # non abbia solo proprietà di estensione ma anche estensioni statiche per me dimostra che è almeno una buona idea.
Richiban,

2
Ce ne dovrebbe essere uno
Rootel

Risposte:


366

Per il momento non è ancora supportato dal compilatore Roslyn out of the box ...

Fino ad ora, le proprietà dell'estensione non erano considerate sufficientemente valide per essere incluse nelle versioni precedenti dello standard C #. C # 7 e C # 8.0 lo hanno visto come campione della proposta ma non è stato ancora rilasciato, soprattutto perché anche se c'è già un'implementazione, vogliono farlo fin dall'inizio.

Ma sarà ...

C'è un elemento dei membri di estensione nell'elenco di lavoro di C # 7, quindi potrebbe essere supportato nel prossimo futuro. Lo stato corrente della proprietà dell'estensione può essere trovato su Github nella voce relativa .

Tuttavia, esiste un argomento ancora più promettente che è "estendere tutto" con particolare attenzione alle proprietà e alle classi statiche o persino ai campi.

Inoltre puoi usare una soluzione alternativa

Come specificato in questo articolo , è possibile utilizzare la TypeDescriptorfunzionalità per collegare un attributo a un'istanza di oggetto in fase di esecuzione. Tuttavia, non utilizza la sintassi delle proprietà standard.
È un po 'diverso dal semplice zucchero sintattico che aggiunge la possibilità di definire una proprietà estesa
string Data(this MyClass instance)come un alias per il metodo di estensione
string GetData(this MyClass instance)mentre memorizza i dati nella classe.

Spero che C # 7 fornirà un'estensione completa di tutto (proprietà e campi), tuttavia su quel punto, solo il tempo potrà dirlo.

E sentiti libero di contribuire dato che il software di domani verrà dalla community.

Aggiornamento: agosto 2016

Come dotnet team ha pubblicato le novità di C # 7.0 e da un commento di Mads Torgensen :

Proprietà dell'estensione: abbiamo avuto un stagista (geniale!) Implementarlo durante l'estate come esperimento, insieme ad altri tipi di membri dell'estensione. Rimaniamo interessati a questo, ma è un grande cambiamento e dobbiamo sentirci sicuri che ne valga la pena.

Sembra che le proprietà di estensione e altri membri siano ancora buoni candidati da includere in una versione futura di Roslyn, ma forse non quella 7.0.

Aggiornamento: maggio 2017

I membri dell'estensione sono stati chiusi come duplicati dell'estensione di tutto ciò che è stato chiuso. La discussione principale era in effetti sull'estensibilità del tipo in senso lato. La funzione è ora tracciata qui come proposta ed è stata rimossa dalla pietra miliare 7.0 .

Aggiornamento: agosto 2017 - Funzionalità proposta di C # 8.0

Mentre rimane ancora solo una funzionalità proposta , ora abbiamo una visione più chiara di quale sarebbe la sua sintassi. Tieni presente che questa sarà la nuova sintassi anche per i metodi di estensione:

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;

Simile alle classi parziali, ma compilato come classe / tipo separato in un assembly diverso. Nota che potrai anche aggiungere membri e operatori statici in questo modo. Come menzionato nel podcast di Mads Torgensen , l'estensione non avrà alcun stato (quindi non può aggiungere membri di istanze private alla classe), il che significa che non sarà possibile aggiungere dati di istanze private collegati all'istanza . Il motivo invocato per questo è che implicherebbe la gestione dei dizionari interni e potrebbe essere difficile (gestione della memoria, ecc ...). Per questo, puoi ancora usare la tecnica TypeDescriptor/ ConditionalWeakTabledescritta in precedenza e con l'estensione della proprietà, la nasconde sotto una bella proprietà.

La sintassi è ancora soggetta a modifiche in quanto implica questo problema . Ad esempio, extendspotrebbe essere sostituito dal forquale alcuni potrebbero sentirsi più naturali e meno correlati a Java.

Aggiornamento dicembre 2018 - Ruoli, estensioni e membri dell'interfaccia statica

L'estensione di tutto non è arrivata a C # 8.0, a causa di alcuni svantaggi spiegati come la fine di questo ticket GitHub . Quindi, c'è stata un'esplorazione per migliorare il design. Qui , Mads Torgensen spiega quali sono i ruoli e le estensioni e come differiscono:

I ruoli consentono l'implementazione delle interfacce su valori specifici di un determinato tipo. Le estensioni consentono di implementare le interfacce su tutti i valori di un determinato tipo, all'interno di una specifica regione di codice.

Può essere visto in una divisione della proposta precedente in due casi d'uso. La nuova sintassi per l'estensione sarebbe così:

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

allora saresti in grado di fare questo:

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

E per un'interfaccia statica :

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

Aggiungere una proprietà extension su inte trattare il intcome IMonoid<int>:

public extension IntMonoid of int : IMonoid<int>
{
    public static int Zero => 0;
}

58
Questa è una delle risposte più utili che abbia mai seguito su StackExchange. Aggiornarsi costantemente con lo stato e tenere tutti informati che ritorna su questo, fornendo solidi collegamenti alla discussione e alla storia.
Il

25
È fantastico che tu lo tenga aggiornato - grazie
David Thielen,

1
Sfortunatamente a partire da questo commento, i ruoli, le estensioni e i membri dell'interfaccia statica sono contrassegnati solo per C # 11 :(
Ian Kemp

436

No, non esistono in C # 3.0 e non verranno aggiunti in 4.0. È nell'elenco delle funzionalità richieste per C #, quindi potrebbe essere aggiunto in una data futura.

A questo punto il meglio che puoi fare è metodi di estensione in stile GetXXX.


3
Allo stesso modo con le proprietà generiche: devi usare la sintassi 'GetXXX <>'.
Jay Bazuzi,

3
ok, è quello che pensavo. @Jay, sì, lo odio anche io, hehe. Soprattutto l'incapacità di avere un generico indicizzatore ... sigh
Svish

75
Link all'elenco delle funzionalità richieste?
Dan Esparza,

2
Che dire della versione 6.0 e 7.0?
Falk

2
Eventuali aggiornamenti su questo a partire dal 2020?
Chad

265

No, non esistono.

So che il team C # li stava prendendo in considerazione ad un certo punto (o almeno lo era Eric Lippert) - insieme ai costruttori e agli operatori di estensione (questi potrebbero impiegare un po 'a rimettersi in testa, ma sono fantastici ...) Tuttavia, non ho Ho visto alcuna prova che faranno parte di C # 4.


EDIT: Non sono apparsi in C # 5, e a partire da luglio 2014 non sembra nemmeno essere in C # 6.

Eric Lippert , il principale sviluppatore del team di compilatori C # di Microsoft a novembre 2012, ha scritto questo blog nell'ottobre 2009:


2
Sì, e potrebbero comunque nascondere il campo: l'impostazione di una singola proprietà potrebbe impostare due proprietà al di sotto o viceversa. (Immagina qualcosa con una proprietà Dimensione normale e proprietà di estensione Larghezza / Altezza, o viceversa.) Sarebbero più utili come quelli di sola lettura, sospetto.
Jon Skeet,

23
Non puoi associarti a metodi di estensione ... essere in grado di aggiungere le tue proprietà per il databinding potrebbe essere utile in molte situazioni.
Nick,

3
@leppie - Il valore delle estensioni di proprietà trarrebbe maggior beneficio dalle proprietà bool e stringhe più che penso. Sbarazzarsi di ()alla fine è molto più leggibile. Lo so per me personalmente, almeno il 90% delle estensioni che scrivo sono di quei 2 tipi.
Codice Maverick,

4
Per fare un esempio del perché questo sarebbe utile, ho un modello EFCF. In alcune delle classi ho proprietà di sola lettura che utilizzo per restituire informazioni formattate: FullName= FirstName + LastName, ShortName= FirstName + LastName[0]. Vorrei aggiungere più di queste proprietà, ma non voglio "sporcare" le classi reali. In questo caso una proprietà di estensione, che è di sola lettura, è perfetta perché posso aggiungere la funzionalità, mantenere pulita la classe principale ed esporre ancora le informazioni che voglio esporre nell'interfaccia utente.
Gup3rSuR4c,

4
@JonSkeet: Hai ragione, ho finito per fare quello che volevo creando la mia classe, quindi avvolgendo tutti i metodi e le proprietà della classe sigillata rilevanti, fornendo quindi static implicit operator FileInfo(FileInfoEx fex)quale restituiva il mio oggetto FileInfo contenuto. Questo mi consente di trattare FileInfoEx come se ereditasse da FileInfo, anche se quella classe è sigillata.
Steve L,

27

Aggiornamento (grazie a @chaost per aver segnalato questo aggiornamento):

Mads Torgersen: "Estensione, tutto non è riuscito a trasformarsi in C # 8.0. È stato" catturato ", se vuoi, in un dibattito molto eccitante sull'ulteriore futuro della lingua, e ora vogliamo assicurarci di non farlo aggiungilo in un modo che inibisca quelle possibilità future. A volte il design del linguaggio è un gioco molto lungo! "

Fonte: sezione commenti in https://blogs.msdn.microsoft.com/dotnet/2018/11/12/building-c-8-0/


Ho smesso di contare quante volte nel corso degli anni ho aperto questa domanda con la speranza di vederlo implementato.

Bene, finalmente possiamo rallegrarci tutti! Microsoft lo introdurrà nella loro prossima versione C # 8.

Quindi invece di farlo ...

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

Finalmente saremo in grado di farlo in questo modo ...

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

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


3
Questa settimana sono state annunciate le funzionalità di C # 8.0 e purtroppo non ho visto nulla di tutto ciò.
Mateo Torres-Ruiz,

1
@ MateoTorres-Ruiz Un commento di 'Mads Torgersen' (sviluppatore C #), che ha risposto a qualcuno che lo chiedeva (3 giorni fa): "Estensione tutto non è riuscito a trasformarsi in C # 8.0. È stato" catturato ", se vuoi , in un dibattito molto eccitante sull'ulteriore futuro della lingua, e ora vogliamo assicurarci di non aggiungerlo in modo da inibire quelle possibilità future. A volte il design della lingua è un gioco molto lungo! " Si sente male .. (Leggi questo sul link Korayems, nella sezione commenti)
Chaost

8

Come menzionato @Psyonity, è possibile utilizzare la tabella condizionale Weak per aggiungere proprietà agli oggetti esistenti. In combinazione con ExpandoObject dinamico, è possibile implementare le proprietà dell'estensione dinamica in poche righe:

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

Un esempio di utilizzo è nei commenti XML:

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

La migliore risposta
N73k,

1

Perché di recente ne avevo bisogno, ho cercato l'origine della risposta in:

c # estende la classe aggiungendo proprietà

e creato una versione più dinamica:

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

Probabilmente può essere migliorato molto (denominazione, dinamica invece di stringa), attualmente lo uso in CF 3.5 insieme a una tabella di condizionale instabile ( https://gist.github.com/Jan-WillemdeBruyn/db79dd6fdef7b9845e217958db98c4d4 )


Siamo spiacenti, ma sebbene appaia molto approfondito, non ha nulla a che fare con le proprietà dell'estensione, ma mostra solo i metodi di estensione.
Viking
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.