c#有扩展属性吗?

例如,我可以添加一个扩展属性的DateTimeFormatInfo称为ShortDateLongTimeFormat将返回ShortDatePattern + " " + LongTimePattern?


当前回答

不,它们在c# 3.0中不存在,也不会在4.0中添加。它在c#需要的特性列表中,所以将来可能会添加它。

在这一点上,你能做的最好的是GetXXX样式的扩展方法。

其他回答

不,它们在c# 3.0中不存在,也不会在4.0中添加。它在c#需要的特性列表中,所以将来可能会添加它。

在这一点上,你能做的最好的是GetXXX样式的扩展方法。

正如@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

更新(感谢@chaos指出此更新):

Mads Torgersen:“c# 8.0并没有扩展所有东西。如果你愿意的话,它“陷入”了一场关于语言未来发展的非常激动人心的辩论,现在我们想确保我们添加它的方式不会抑制这些未来的可能性。有时候语言设计是一场漫长的游戏!”

来源:https://blogs.msdn.microsoft.com/dotnet/2018/11/12/building-c-8-0/的评论部分


我不再计算这些年来我有多少次提出这个问题,希望看到它得到实现。

好吧,我们终于都可以高兴了!微软将在他们即将发布的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/

不,他们不存在。

我知道c#团队曾经考虑过它们(或者至少Eric Lippert曾经考虑过)——还有扩展构造函数和操作符(你可能需要一段时间才能理解它们,但是很酷……)然而,我还没有看到任何证据表明它们将成为c# 4的一部分。


编辑:它们没有出现在c# 5中,到2014年7月,看起来也不会出现在c# 6中。

Eric Lippert,微软c#编译器团队2012年11月的首席开发人员,在2009年10月写了一篇博文:

为什么没有扩展属性?-编程中的奇妙冒险

目前Roslyn编译器仍然不支持开箱即用…

到目前为止,扩展属性还没有被认为有足够的价值,不能包含在以前版本的c#标准中。各种c#版本(也许是所有的?)都把它视为提案的冠军,但它还没有发布,最主要的原因是,即使已经有了一个实现,他们也想让它正确。

但它可能会……有一天……

2022年更新:

这些特性似乎还在讨论中。

此外,您可以使用一个变通办法

如本文所述,您可以使用TypeDescriptor功能在运行时将属性附加到对象实例。但是,它没有使用标准属性的语法。 这与语法糖略有不同,它添加了一种可能性,可以将字符串Data(这个MyClass实例)这样的扩展属性定义为扩展方法字符串GetData(这个MyClass实例)的别名,因为它将数据存储到类中。

我希望c#很快就能提供所有功能齐全的扩展(属性和字段),但这一点只有时间能告诉我们。

明天的软件将来自于社区,请尽情地贡献自己的力量。

后处理方法

如果允许这样做,你也可以在编译后使用PostSharp、Mono等工具在程序集中动态地添加类的属性。Cecil(或类似的代码/IL重写工具)。

然而,正如一名开发人员在上面的讨论中解释的那样, 重写代码不会让编译器知道你的意图,因此它可能无法优化你的结果代码。作为一种语言特性,预期效果会更好。

一点历史

在c# 7工作列表中有一个扩展成员项,所以我认为在不久的将来可能会支持它。扩展属性的当前状态可以在Github上的相关项下找到。

然而,还有一个更有前途的主题,即“扩展一切”,特别是关注属性和静态类甚至字段。

更新:2016年8月

dotnet团队发布了c# 7.0的新特性,并引用了Mads Torgensen的评论:

扩展属性:我们有一个实习生实现了它们 这个夏天作为一个实验,以及其他形式的延伸 成员。我们仍然对此感兴趣,但这是一个很大的变化,我们 你得确信这是值得的。

似乎扩展属性和其他成员仍然是Roslyn未来版本中很好的候选,但可能不是7.0版本。

更新:2017年5月

扩展成员已被关闭作为复制的扩展一切问题,这是关闭的。 实际上,主要讨论的是广义上的类型可扩展性。 该功能现在作为提案跟踪,并已从7.0里程碑中删除。

更新:2017年8月- 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;

Similar to partial classes, but compiled as a separate class/type in a different assembly. Note you will also be able to add static members and operators this way. As mentioned in Mads Torgensen podcast, the extension won't have any state (so it cannot add private instance members to the class) which means you won't be able to add private instance data linked to the instance. The reason invoked for that is it would imply to manage internally dictionaries and it could be difficult (memory management, etc...). For this, you can still use the TypeDescriptor/ConditionalWeakTable technique described earlier and with the property extension, hides it under a nice property.

语法仍然可能发生变化,这暗示了这个问题。例如,扩展可以被替换为,其中一些可能感觉更自然,与java的关系更少。

更新2018年12月-角色,扩展和静态接口成员

由于本GitHub票据的结尾解释了一些缺陷,所有扩展都没有到c# 8.0。因此,我们对改进设计进行了探索。在这里,Mads Torgensen解释了什么是角色和扩展,以及它们之间的区别:

角色允许在给定的特定值上实现接口 类型。的所有值上实现接口 给定类型,在特定的代码区域内。

可以在两个用例中看到以前的建议的分裂。扩展的新语法是这样的:

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