Есть ли в C # свойства расширения?

769

Есть ли в C # свойства расширения?

Например, я могу добавить свойство extension к DateTimeFormatInfoвызываемому объекту, ShortDateLongTimeFormatкоторый будет возвращать ShortDatePattern + " " + LongTimePattern?

Svish
источник
14
Я хотел добавить метод расширения с именем IsNull для Nullable <T>, который просто вернется! HasValue. .IsNull () определенно менее красива, чем .IsNull
Кен
1
Я считаю это полезным для тринарного оператора?
PedroC88
2
Я хотел, чтобы это имитировало Java, enumкоторые могут иметь свойства и методы. В C # enumне может быть свойств или методов, но вы можете создавать методы расширения для них. Этот вопрос был полезен для меня и не должен быть закрыт.
Ян Маклаирд
Хотя, как говорили многие люди, в настоящее время нет никаких планов по добавлению этого в язык, нет никаких причин, по которым это невозможно сделать. Тот факт, что F # имеет для меня не только свойства расширения, но и статические расширения, доказывает, что это по крайней мере хорошая идея.
Ричибан
2
Там должно быть сделано один
Rootel

Ответы:

366

На данный момент это все еще не поддерживается из коробки компилятором Roslyn ...

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

Но это будет ...

В рабочем списке C # 7 есть элемент расширения, поэтому он может быть поддержан в ближайшем будущем. Текущий статус свойства extension можно найти на Github под соответствующим элементом .

Тем не менее, есть еще более многообещающая тема, которая заключается в том, чтобы «расширить все», уделяя особое внимание свойствам, статическим классам или даже полям.

Кроме того, вы можете использовать обходной путь

Как указано в этой статье , вы можете использовать TypeDescriptorвозможность прикрепить атрибут к экземпляру объекта во время выполнения. Однако он не использует синтаксис стандартных свойств.
Это немного отличается от простого синтаксического сахара, добавляющего возможность определять расширенное свойство, например,
string Data(this MyClass instance)в качестве псевдонима для метода расширения, так
string GetData(this MyClass instance)как оно хранит данные в классе.

Я надеюсь, что C # 7 обеспечит полнофункциональное расширение всего (свойств и полей), однако на данный момент только время покажет.

И не стесняйтесь вносить свой вклад, поскольку программное обеспечение завтрашнего дня придет от сообщества.

Обновление: август 2016

Как команда dotnet опубликовала, что нового в C # 7.0 и из комментария Мадса Торгенсена :

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

Кажется, что свойства расширения и другие члены все еще являются хорошими кандидатами для включения в будущий выпуск 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;
}
потрясающий
источник
58
Это один из самых полезных ответов, которые я когда-либо использовал на StackExchange. Постоянно обновляется со статусом и держит всех в курсе, что возвращается к этому, обеспечивая прочные ссылки на обсуждение и историю.
bdrelling
25
Это потрясающе, что вы держите это в курсе - спасибо
Дэвид Тилен
1
К сожалению, на данный момент роли, расширения и члены статического интерфейса помечены только для C # 11 :(
Ян Кемп
436

Нет, они не существуют в C # 3.0 и не будут добавлены в 4.0. Он находится в списке желаний для C #, поэтому он может быть добавлен в будущем.

На данный момент лучшее, что вы можете сделать, - это методы расширения стиля GetXXX.

JaredPar
источник
3
Аналогично с общими свойствами: вы должны использовать синтаксис GetXXX <>.
Джей Базузи
3
хорошо, я так и думал. @ Джей, да, я тоже это ненавижу, хе-хе. Особенно невозможность иметь общий индексатор ... вздох
Svish
75
Ссылка на список функций хочет?
Дэн Эспарза
2
А как насчет версии 6.0 и 7.0?
Фальк
2
Какие-либо обновления по этому вопросу к 2020 году?
Чад
265

Нет, их не существует.

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


РЕДАКТИРОВАТЬ: они не появлялись в C # 5, и по состоянию на июль 2014 года, похоже, что он не будет в C # 6.

Эрик Липперт (Eric Lippert) , главный разработчик команды компилятора C # в Microsoft до ноября 2012 года, сообщил об этом в октябре 2009 года:

Джон Скит
источник
2
Да, и они все еще могут скрывать поле - установка одного свойства может установить два свойства внизу или наоборот. (Представьте себе что-то с обычным свойством Size и свойствами расширения Width / Height, или наоборот.) Хотя, я подозреваю, они были бы более полезны как доступные только для чтения.
Джон Скит
23
Вы не можете привязать методы расширения ... возможность добавлять свои собственные свойства для привязки данных может быть полезной во многих ситуациях.
Ник
3
@leppie - Больше всего мне кажется, что значения расширений свойств будут полезны для свойств bool и string. Избавление от ()в конце гораздо более читабельно. Я лично знаю, что по крайней мере 90% написанных мной расширений относятся к этим двум типам.
Код Maverick
4
Чтобы привести пример того, почему это было бы полезно, у меня есть модель EFCF. В некоторых классах у меня есть свойства только для чтения, которые я использую для возврата отформатированной информации: FullName= FirstName + LastName, ShortName= FirstName + LastName[0]. Я хотел бы добавить больше этих свойств, но я не хочу «загрязнять» реальные классы. В этом случае свойство расширения, предназначенное только для чтения, идеально подходит, потому что я могу добавить функциональность, сохранить основной класс в чистоте и по-прежнему предоставлять информацию, которую я хочу предоставить в пользовательском интерфейсе.
Gup3rSuR4c
4
@JonSkeet: Вы правы, в итоге я сделал то, что хотел, создав собственный класс, обернув все соответствующие методы и свойства запечатанного класса, и предоставив, static implicit operator FileInfo(FileInfoEx fex)который возвращает мой содержащийся объект FileInfo. Это позволяет мне обрабатывать FileInfoEx так, как если бы он наследовал от FileInfo, даже если этот класс запечатан.
Стив Л
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/

Korayem
источник
3
На этой неделе были объявлены возможности C # 8.0, и я, к сожалению, не увидел ничего подобного.
Матео Торрес-Руис
1
@ MateoTorres-Ruiz Комментарий от 'Mads Torgersen' (C # dev), отвечающий кому-то, спрашивающему об этом (3 дня назад): «Расширение все не попало в C # 8.0. Он« догнал », если хотите В очень захватывающих дебатах о будущем будущем языка, и теперь мы хотим убедиться, что мы не добавим его таким образом, который ограничивает эти будущие возможности. Иногда языковой дизайн - очень длинная игра! " Чувствует себя плохо .. (Прочтите это по ссылке Korayems, в разделе комментариев)
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
realbart
источник
Лучший ответ
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 )

Psyonity
источник
Извините, но хотя это выглядит очень тщательно, оно не имеет ничего общего со свойствами расширения, а только показывает методы расширения.
Викинг