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

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

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

Ответ 1

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

На этом этапе лучше всего использовать методы расширения стиля GetXXX.

Ответ 2

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

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

EDIT: они не появлялись на С# 5, и по состоянию на июль 2014 года не похоже, что это будет на С# 6.

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

Ответ 3

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

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

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

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

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

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

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

Надеюсь, что С# 7 предоставит полнофункциональное расширение всем (свойствам и полям), однако на этот момент покажет только время.

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

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

Поскольку команда dotnet опубликовала новое в С# 7.0 и из комментария Mads Torgensen:

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

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

Обновление: май 2017 г.

Члены расширения были закрыты как дубликаты расширения, все вопросы, которые тоже закрыты. Основное обсуждение на самом деле касалось расширяемости Типа в широком смысле. Функция теперь отслеживается здесь как предложение и была удалена с 7.0-этапов.

Обновление: август 2017 г. - предлагаемая функция С# 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 - Роли, Расширения и элементы статического интерфейса

Расширение все не дошло до С# 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;
}

Ответ 4

Обновление (благодаря @chaost для указания этого обновления):

Mads Torgersen: "Расширение все не попало в С# 8.0. Он был" догнал ", если хотите, в очень увлекательной дискуссии о будущем будущем языка, и теперь мы хотим убедиться, что мы не добавляем его в способ, который препятствует этим будущим возможностям. Иногда дизайн языка - очень длинная игра! "

Источник: раздел комментариев в https://blogs.msdn.microsoft.com/dotnet/2018/11/12/building-c-8-0/


Я перестал считать, сколько раз за эти годы я открыл этот вопрос, надеясь, что это произойдет.

Ну, наконец, мы все можем радоваться! Microsoft собирается представить это в своем предстоящем выпуске С# 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

Как упоминалось в @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

Ответ 6

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

С# расширить класс, добавив свойства

и создал более динамичную версию:

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)