Чи має C # властивості розширення?


768

Чи має C # властивості розширення?

Наприклад, чи можна додати властивість розширення до DateTimeFormatInfoвиклику, ShortDateLongTimeFormatякий би повертався ShortDatePattern + " " + LongTimePattern?


14
Я хотів додати метод розширення під назвою IsNull на Nullable <T>, який би просто повернувся! HasValue. .IsNull (), безумовно, менш гарна, ніж .IsNull
Кен

1
Я вважаю це корисним для тринаціонального оператора?
PedroC88

2
Я хотів, щоб це імітувало Java, enumякі можуть мати властивості та методи. У C # 's enumне може бути властивостей або методів, але ви можете створити на них методи розширення. Це питання було корисним для мене, і його не слід закривати.
Ian McLaird

Хоча, як багато людей сказали, наразі немає планів додавати це до мови, немає жодної причини, щоб цього не було зроблено. Той факт, що F # має не тільки властивості розширення, але й статичні розширення, мені також підтверджує, що це принаймні гарна ідея.
Річібан

2
Слід зробити один
Rootel

Відповіді:


365

Наразі компілятор Roslyn він все ще не підтримується з коробки ...

До цих пір властивості розширення не розглядалися як такі цінні, щоб їх можна було включити в попередні версії стандарту C #. C # 7 і C # 8.0 вважали це чемпіоном пропозицій, але його ще не було випущено, тим більше, що навіть якщо вже є реалізація, вони хочуть зробити це з самого початку.

Але це буде ...

У робочому списку C # 7 є пункт членів розширення, тому він може бути підтриманий найближчим часом. Поточний статус властивості розширення можна знайти в Github під відповідним пунктом .

Однак є ще більш перспективна тема - це "розширити все" з акцентом на особливо властивості та статичні класи чи навіть поля.

Крім того, ви можете використовувати вирішення

Як зазначено в цій статті , ви можете використовувати TypeDescriptorможливість приєднання атрибута до екземпляра об'єкта під час виконання. Однак він не використовує синтаксис стандартних властивостей.
Це трохи відрізняється від просто синтаксичного цукру, додаючи можливість визначити розширене властивість, наприклад
string Data(this MyClass instance), псевдонім для методу розширення,
string GetData(this MyClass instance)оскільки він зберігає дані в клас.

Я сподіваюся, що C # 7 надасть повне розширене розширення для всіх (властивостей та полів), проте з цього приводу покаже лише час.

І не соромтесь робити внесок, оскільки програмне забезпечення завтра буде надходити від громади.

Оновлення: серпень 2016 року

Як дотнет команда опублікувала, що нового в C # 7.0 та з коментаря Mads Torgensen :

Властивості розширення: у нас (геніальний!) Стажер впроваджував їх протягом літа як експеримент разом з іншими видами членів розширення. Ми все ще зацікавлені в цьому, але це велика зміна, і нам потрібно впевнено почувати, що воно того варте.

Здається, що властивості розширення та інші члени, як і раніше, є хорошими кандидатами для включення у майбутній випуск Roslyn, але, можливо, не 7.0.

Оновлення: травень 2017 року

Члени розширення закриті як дублікат розширення, і все питання, яке закрито. Основна дискусія була насправді про розширюваність типу в широкому розумінні. Тепер ця функція відстежується тут як пропозиція, і вона була вилучена із 7.0 етапу .

Оновлення: серпень 2017 р. - запропонована функція C # 8.0

Хоча це все ще залишається лише запропонованою функцією, ми маємо чіткіше уявлення про те, яким би був його синтаксис. Майте на увазі, що це буде новий синтаксис і для методів розширення:

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;

Схожий на часткові класи, але складений як окремий клас / тип в іншій збірці. Зауважте, ви також зможете додавати статичні члени та оператори таким чином. Як згадується в подкасті Mads Torgensen , розширення не матиме жодного стану (тому він не може додавати приватних членів екземпляра до класу), що означає, що ви не зможете додавати приватні дані, пов'язані з екземпляром . Причина, на яку посилається, полягає в тому, що це означатиме, що керувати внутрішніми словниками, і це може бути складно (управління пам'яттю тощо). Для цього ви все ще можете використовувати TypeDescriptor/ ConditionalWeakTableописану раніше техніку та з розширенням властивості, ховає її під приємним властивістю.

Синтаксис все ще може бути змінений, як випливає з цього питання . Наприклад, extendsможе бути замінено тим, forщо деякі можуть відчувати себе більш природними та менш java пов'язаними.

Оновлення грудня 2018 року - ролі, розширення та статичні учасники інтерфейсу

Розширення все не призвело до C # 8.0, через деякі недоліки, пояснені як кінець цього квитка на GitHub . Отже, була проведена розвідка для вдосконалення дизайну. Тут Мадс Торгенсен пояснює, що таке ролі та розширення та чим вони відрізняються:

Ролі дозволяють реалізувати інтерфейси за певними значеннями заданого типу. Розширення дозволяють реалізувати інтерфейси для всіх значень заданого типу в межах конкретної області коду.

Це можна побачити при розщепленні попередньої пропозиції у двох випадках використання. Новий синтаксис для розширення буде виглядати так:

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

тоді ви зможете це зробити:

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

А для статичного інтерфейсу :

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

Додати властивість розширення на intі лікувати , intяк IMonoid<int>:

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

57
Це одна з найкорисніших відповідей, яку я коли-небудь дотримувався на StackExchange. Постійне оновлення статусу та інформування всіх, хто повертається до цього, забезпечуючи міцні посилання на дискусії та історію.
bdrelling

25
Дивовижно, що ви
Девід Тілен

1
На жаль, щодо цього коментаря, ролі, розширення та статичні учасники інтерфейсу позначені лише для C # 11 :(
Ian Kemp

436

Ні, вони не існують в C # 3.0 і не будуть додані в 4.0. Він знаходиться у списку бажаних функцій для C #, тому він може бути доданий у майбутньому.

На даний момент найкраще, що ви можете зробити - це методи розширення стилю GetXXX.


3
Аналогічно із загальними властивостями: ви повинні використовувати синтаксис 'GetXXX <>'.
Джей Базузі

3
добре, ось що я подумав. @ Джей, так, я теж ненавиджу це, хе-хе. Особливо неможливість мати загальний індексатор ... зітхання
Свиш

75
Посилання на список потрібних функцій?
Дан Еспарза

2
Що щодо версій 6.0 та 7.0?
Falk

2
Будь-які оновлення щодо цього станом на 2020 рік?
Чад

265

Ні, їх не існує.

Я знаю, що команда C # розглядала їх в один момент (або, принаймні, Еріка Ліпперта) - разом із конструкторами розширень та операторами (на це може знадобитися певний час, аби прокрутити голову, але це круто ...) Однак у мене немає Я не бачив жодних доказів того, що вони будуть частиною C # 4.


EDIT: Вони не з'явилися в C # 5, і станом на липень 2014 року, схоже, це також не буде в C # 6.

Ерік Ліпперт , головний розробник у команді компіляторів C # Microsoft у листопаді 2012 року, про це розповів у жовтні 2009 року:


2
Так, і вони все ще можуть приховати поле - встановлення однієї властивості може встановити дві властивості під ними, або навпаки. (Уявіть собі щось із властивістю нормального розміру та властивостями розширення Width / Height або навпаки.) Вони будуть більш корисні як ті, які є лише для читання, але, підозрюю.
Джон Скіт

23
Ви не можете прив’язатись до методів розширення ... можливість додати власні властивості для прив'язки даних може бути корисною у багатьох ситуаціях.
Нік

3
@leppie - Значення розширень властивостей принесло б перевагу властивостям bool та string, як я вважаю. Позбавлення від ()кінця набагато читабельніше. Я особисто знаю, щонайменше 90% розширень, які я пишу, мають ці 2 типи.
Code Maverick

4
Щоб навести приклад, чому це було б корисно, у мене є модель EFCF. У деяких класах у мене є властивості лише для читання, які я використовую для повернення відформатованої інформації: FullName= FirstName + LastName, ShortName= FirstName + LastName[0]. Я хотів би додати більше цих властивостей, але я не хочу "бруднити" фактичні класи. У цьому випадку властивість розширення, лише для читання, є ідеальною, оскільки я можу додати функціонал, підтримувати основний клас чистим і все-таки викривати інформацію, яку хочу викрити в інтерфейсі.
Gup3rSuR4c

4
@JonSkeet: Ти маєш рацію, я закінчив робити те, що хотів, створивши свій власний клас, потім обернув усі відповідні запечатані методи та властивості класу, надавши потім static implicit operator FileInfo(FileInfoEx fex)який повертає мій об'єкт FileInfo. Це ефективно дозволяє мені ставитися до FileInfoEx так, ніби він успадковується від FileInfo, навіть якщо цей клас запечатаний.
Стів L

27

Оновлення (завдяки @chaost, що вказав це оновлення):

Мадс Торгерсен: "Розширення все не встигло перетворитись на C # 8.0. Якщо це станеться," натрапили "на дуже захоплюючу дискусію щодо подальшого майбутнього мови, і тепер ми хочемо переконатися, що ми цього не зробимо додайте його таким чином, що гальмує ці майбутні можливості. Іноді дизайн мови - це дуже довга гра! "

Джерело: розділ коментарів у https://blogs.msdn.microsoft.com/dotnet/2018/11/12/building-c-8-0/


Я перестала рахувати, скільки разів за ці роки я відкривала це питання з надією побачити це реалізованим.

Ну, нарешті ми всі можемо радіти! Microsoft збирається представити це у своїй наступній версії C # 8.

Тож замість цього робити ...

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

Ми нарешті зможемо зробити це так ...

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

Джерело: https://blog.ndepend.com/c-8-0-features-glimpse-future/


3
Цього тижня було оголошено функції C # 8.0 , і я, на жаль, не побачив нічого з цього.
Матео Торрес-Руїс

1
@ MateoTorres-Ruiz Коментар від "Mads Torgersen" (C # dev), відповідаючи на запитання про це (3 дні тому): "Розширення все не перетворило його на C # 8.0. Це" наздогнало ", якщо ви хочете , у дуже захоплюючій дискусії щодо подальшого майбутнього мови, і тепер ми хочемо переконатися, що ми не додамо її таким чином, що гальмує ці майбутні можливості. Іноді дизайн мови - це дуже довга гра! " Почувається погано .. (Прочитайте це за посиланням на Кораєм, у розділі коментарів)
Chaost

8

Як було зазначено @Psyonity, ви можете використовувати conditionalWeakTable для додавання властивостей до існуючих об'єктів. У поєднанні з динамічним ExpandoObject ви можете реалізувати властивості динамічного розширення в декілька рядків:

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

Приклад використання - у коментарях 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

Найкраща відповідь
N73k

1

Оскільки мені нещодавно це було потрібно, я переглянув джерело відповіді:

c # розширити клас, додавши властивості

і створили більш динамічну версію:

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

Можливо, це може бути значно вдосконалено (іменування, динамічне замість рядка), я зараз використовую це в CF 3.5 разом із витримним ConditionalWeakTable ( https://gist.github.com/Jan-WillemdeBruyn/db79dd6fdef7b9845e217958db98c4d4 )


Вибачте, але, хоча це виглядає дуже ретельно, воно не має нічого спільного з властивостями розширення, а лише показує методи розширення.
Вікінг
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.