Есть ли в C # свойства расширения? - PullRequest
680 голосов
/ 06 марта 2009

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

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

Ответы [ 6 ]

414 голосов
/ 06 марта 2009

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

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

260 голосов
/ 06 марта 2009

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

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

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

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

212 голосов
/ 24 января 2016

На данный момент он все еще не поддерживается из коробки компилятором 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 и из комментария 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 podcast , расширение не будет иметь никакого состояния (поэтому оно не может добавлять частные элементы экземпляра в класс), что означает, что вы не сможете добавлять частные Данные экземпляра связаны с экземпляром . Причина, по которой это вызвано, заключается в том, что это подразумевает управление внутренними словарями, и это может быть сложно (управление памятью и т. Д.). Для этого вы все еще можете использовать метод 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;
}
24 голосов
/ 04 декабря 2017

Обновление (спасибо @ 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/

5 голосов
/ 18 апреля 2017

Как уже упоминалось @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
2 голосов
/ 12 января 2017

Поскольку я недавно нуждался в этом, я посмотрел на источник ответа в:

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)

...