Czy C # ma właściwości rozszerzenia?


768

Czy C # ma właściwości rozszerzenia?

Na przykład, czy mogę dodać właściwość rozszerzenia do DateTimeFormatInfowywołanej, ShortDateLongTimeFormatktóra zwróci ShortDatePattern + " " + LongTimePattern?


14
Chciałem dodać metodę rozszerzenia o nazwie IsNull na Nullable <T>, która po prostu wróci! HasValue. .IsNull () jest zdecydowanie mniej ładna niż .IsNull
Ken

1
Uważam to za przydatne dla operatora ?
trójdzielnego

2
Chciałem, aby to naśladowało Java, enumktóre mogą mieć właściwości i metody. C # 's enums nie może mieć właściwości lub metody, ale można utworzyć metody rozszerzenie na nich. To pytanie było dla mnie przydatne i nie powinno być zamknięte.
Ian McLaird

Chociaż, jak wiele osób mówi, obecnie nie ma planów, aby dodać to do języka, nie ma powodu, dla którego nie można tego zrobić. Fakt, że F # ma nie tylko właściwości rozszerzeń, ale także rozszerzenia statyczne, dowodzi, że jest to co najmniej dobry pomysł.
Richiban

2
Powinien być jeden
Rootel

Odpowiedzi:


365

W tej chwili kompilator Roslyn nie obsługuje go już po wyjęciu z pudełka ...

Do tej pory właściwości rozszerzenia nie były postrzegane jako wystarczająco wartościowe, aby można je było uwzględnić w poprzednich wersjach standardu C #. C # 7 i C # 8.0 postrzegają to jako mistrza propozycji, ale nie zostało jeszcze wydane, przede wszystkim dlatego, że nawet jeśli istnieje już implementacja, chcą to zrobić od samego początku.

Ale to będzie ...

Na liście prac w C # 7 znajduje się element rozszerzenia, więc może być obsługiwany w najbliższej przyszłości. Bieżący status właściwości rozszerzenia można znaleźć na Github w powiązanej pozycji .

Istnieje jednak jeszcze bardziej obiecujący temat, którym jest „rozszerzenie wszystkiego” ze szczególnym uwzględnieniem właściwości i klas statycznych, a nawet pól.

Ponadto możesz użyć obejścia

Jak określono w tym artykule , można użyć TypeDescriptormożliwości dołączenia atrybutu do instancji obiektu w czasie wykonywania. Jednak nie używa składni standardowych właściwości.
Różni się nieco od cukru syntaktycznego, dodając możliwość zdefiniowania rozszerzonej właściwości, takiej
string Data(this MyClass instance)jak alias metody rozszerzenia,
string GetData(this MyClass instance)ponieważ przechowuje dane w klasie.

Mam nadzieję, że C # 7 zapewni w pełni funkcjonalne rozszerzenie wszystkiego (właściwości i pola), jednak w tym momencie tylko czas pokaże.

I nie krępuj się przyłączyć, ponieważ oprogramowanie jutra będzie pochodziło od społeczności.

Aktualizacja: sierpień 2016 r

Gdy zespół dotnet opublikował nowości w C # 7.0 oraz komentarz Madsa Torgensena :

Właściwości rozszerzenia: mieliśmy (genialnego!) Stażystę, który wdrożył je latem jako eksperyment, wraz z innymi rodzajami członków rozszerzenia. Nadal jesteśmy tym zainteresowani, ale to duża zmiana i musimy mieć pewność, że warto.

Wygląda na to, że właściwości rozszerzenia i inni członkowie nadal są dobrymi kandydatami do włączenia w przyszłej wersji Roslyn, ale może nie w wersji 7.0.

Aktualizacja: maj 2017 r

Elementy rozszerzenia zostały zamknięte jako duplikat rozszerzenia wszystko, co również zostało zamknięte. Główna dyskusja dotyczyła w gruncie rzeczy rozszerzalności typu. Funkcja jest teraz śledzona tutaj jako propozycja i została usunięta z kamienia milowego 7.0 .

Aktualizacja: sierpień 2017 - proponowana funkcja C # 8.0

Chociaż nadal pozostaje tylko proponowaną funkcją, mamy teraz wyraźniejszy pogląd na jego składnię. Pamiętaj, że będzie to również nowa składnia metod rozszerzeń:

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;

Podobne do klas częściowych, ale skompilowane jako osobna klasa / typ w innym zestawie. Uwaga: w ten sposób będziesz mógł dodawać statyczne elementy i operatory. Jak wspomniano w podcastie Madsa Torgensena , rozszerzenie nie będzie miało żadnego stanu (więc nie może dodawać prywatnych członków instancji do klasy), co oznacza, że ​​nie będzie można dodawać danych prywatnych instancji powiązanych z instancją . Powodem jest to, że sugerowałoby to zarządzanie wewnętrznymi słownikami i może być trudne (zarządzanie pamięcią itp.). W tym celu możesz nadal korzystać z opisanej wcześniej techniki TypeDescriptor/ ConditionalWeakTableiz rozszerzeniem właściwości ukrywa ją pod ładną właściwością.

Składnia nadal może ulec zmianie, ponieważ implikuje ten problem . Na przykład extendsmożna je zastąpić forniektórymi, którzy mogą czuć się bardziej naturalni i mniej związani z javą.

Aktualizacja grudzień 2018 r. - Role, rozszerzenia i statyczne elementy interfejsu

Rozszerzenie wszystko nie udało się do C # 8.0, z powodu niektórych wad wyjaśnionych jako koniec tego biletu GitHub . Przeprowadzono więc eksplorację w celu ulepszenia projektu. Tutaj Mads Torgensen wyjaśnia, jakie są role i rozszerzenia oraz czym się różnią:

Role umożliwiają implementację interfejsów na określonych wartościach danego typu. Rozszerzenia pozwalają na implementację interfejsów dla wszystkich wartości danego typu w obrębie określonego regionu kodu.

Można to zaobserwować na podzieleniu poprzedniej propozycji w dwóch przypadkach użycia. Nowa składnia przedłużenie byłoby tak:

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

wtedy będziesz w stanie to zrobić:

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

A dla interfejsu statycznego :

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

Dodawanie właściwości Extension na inti traktować intjako IMonoid<int>:

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

57
Jest to jedna z najbardziej użytecznych odpowiedzi, jakie kiedykolwiek obserwowałem na StackExchange. Ciągłe aktualizowanie statusu i informowanie wszystkich, którzy wrócili do tego, zapewniając solidne linki do dyskusji i historii.
bdrelling

25
To wspaniałe, że aktualizujesz to - dziękuję
David Thielen,

1
Niestety od tego komentarza role, rozszerzenia i statyczne elementy interfejsu są oznaczane tylko dla C # 11 :(
Ian Kemp

436

Nie, nie istnieją w C # 3.0 i nie zostaną dodane w 4.0. Jest na liście potrzebnych funkcji dla C #, więc może zostać dodany w przyszłości.

W tym momencie najlepsze, co możesz zrobić, to metody rozszerzenia stylu GetXXX.


3
Podobnie w przypadku właściwości ogólnych: musisz użyć składni „GetXXX <>”.
Jay Bazuzi

3
ok, tak myślałem. @Jay, tak, też tego nienawidzę, hehe. Zwłaszcza brak możliwości posiadania ogólnego
indeksu

75
Link do listy potrzebnych funkcji?
Dan Esparza

2
Co z wersją 6.0 i 7.0?
Falk

2
Wszelkie aktualizacje na ten temat od 2020 roku?
Czad

265

Nie, nie istnieją.

Wiem, że zespół C # rozważał je w pewnym momencie (a przynajmniej był to Eric Lippert) - wraz z konstruktorami i operatorami rozszerzeń (może to trochę potrwać, ale jesteś fajny ...) Jednak nie mam nie widziałem żadnych dowodów, że będą częścią C # 4.


EDYCJA: Nie pojawiły się w C # 5, a od lipca 2014 roku nie wygląda na to, że będzie w C # 6.

Eric Lippert , główny programista w zespole kompilatorów C # w firmie Microsoft do listopada 2012 r., Napisał o tym na blogu w październiku 2009 r .:


2
Tak, i nadal mogą ukrywać pole - ustawienie pojedynczej właściwości może ustawić dwie właściwości pod spodem lub odwrotnie. (Wyobraź sobie coś z normalną właściwością Rozmiar i właściwościami Szerokość / Wysokość lub odwrotnie.) Są bardziej użyteczne jako tylko do odczytu, podejrzewam.
Jon Skeet

23
Nie można powiązać z metodami rozszerzeń ... możliwość dodania własnych właściwości do wiązania danych może być pomocna w wielu sytuacjach.
Nick

3
@leppie - Wartość rozszerzeń właściwości najbardziej skorzysta z właściwości bool i string. Pozbycie się ()na końcu jest znacznie bardziej czytelne. Wiem dla mnie osobiście, co najmniej 90% rozszerzeń, które piszę, są z tych 2 typów.
Code Maverick,

4
Aby podać przykład, dlaczego byłoby to przydatne, mam model EFCF. W niektórych klasach mam właściwości tylko do odczytu, których używam do zwracania sformatowanych informacji: FullName= FirstName + LastName, ShortName= FirstName + LastName[0]. Chciałbym dodać więcej tych właściwości, ale nie chcę „brudzić” rzeczywistych klas. W tym przypadku właściwość rozszerzenia, która jest tylko do odczytu, jest idealna, ponieważ mogę dodać funkcjonalność, utrzymać klasę główną w czystości i nadal ujawniać informacje, które chcę ujawnić w interfejsie użytkownika.
Gup3rSuR4c

4
@JonSkeet: Masz rację, skończyłem robić to, co chciałem, tworząc własną klasę, a następnie zawijając wszystkie odpowiednie metody i właściwości klas zapieczętowanych, a następnie dostarczając, static implicit operator FileInfo(FileInfoEx fex)który zwraca mój zawarty obiekt FileInfo. To skutecznie pozwala mi traktować FileInfoEx tak, jakby odziedziczył po FileInfo, nawet jeśli ta klasa jest zapieczętowana.
Steve L.

27

Aktualizacja (dzięki @chaost za wskazanie tej aktualizacji):

Mads Torgersen: „Po rozszerzeniu wszystko nie dotarło do C # 8.0. Zostało„ złapane ”, jeśli chcesz, w bardzo ekscytującej debacie na temat dalszej przyszłości języka, a teraz chcemy się upewnić, że nie dodaj go w sposób, który hamuje przyszłe możliwości. Czasami projektowanie języka to bardzo długa gra! ”

Źródło: sekcja komentarzy w https://blogs.msdn.microsoft.com/dotnet/2018/11/12/building-c-8-0/


Przestałem liczyć, ile razy z biegiem lat otworzyłem to pytanie z nadzieją, że je zrealizuję.

W końcu wszyscy możemy się radować! Microsoft zamierza wprowadzić to w swojej nadchodzącej wersji C # 8.

Zamiast robić to ...

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

W końcu będziemy w stanie to zrobić tak ...

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

Źródło: https://blog.ndepend.com/c-8-0-features-glimpse-future/


3
W tym tygodniu ogłoszono funkcje C # 8.0 i niestety nie widziałem żadnej z nich.
Mateo Torres-Ruiz,

1
@ MateoTorres-Ruiz Komentarz od „Mads Torgersen” (C # dev), odpowiadający komuś pytającemu o to (3 dni temu): „Rozszerzenie wszystko nie udało się do C # 8.0. Zostało„ złapane ”, jeśli chcesz , w bardzo ekscytującej debacie na temat dalszej przyszłości języka, a teraz chcemy się upewnić, że nie dodamy go w sposób, który hamuje te przyszłe możliwości. Czasami projektowanie języka jest bardzo długą grą! ” Czuje się źle .. (Przeczytaj to na link Korayems, w sekcji komentarzy)
Chaost

8

Jak wspomniano @Psyonity, możesz użyć conditionalWeakTable, aby dodać właściwości do istniejących obiektów. W połączeniu z dynamicznym ExpandoObject można zaimplementować właściwości dynamicznego rozszerzenia w kilku wierszach:

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

Przykład użycia znajduje się w komentarzach 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

Najlepsza odpowiedź
N73k

1

Ponieważ ostatnio tego potrzebowałem, spojrzałem na źródło odpowiedzi w:

c # rozszerzyć klasę poprzez dodanie właściwości

i stworzył bardziej dynamiczną wersję:

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

Prawdopodobnie można go znacznie poprawić (nazewnictwo, dynamiczne zamiast łańcucha), obecnie używam tego w CF 3.5 wraz z hacky ConditionalWeakTable ( https://gist.github.com/Jan-WillemdeBruyn/db79dd6fdef7b9845e217958db98c4d4 )


Przykro nam, ale chociaż wygląda to bardzo dokładnie, nie ma to nic wspólnego z właściwościami rozszerzenia, ale pokazuje tylko metody rozszerzenia.
Viking,
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.