C#是否具有扩展属性?


768

C#是否具有扩展属性?

例如,是否可以将扩展属性添加到将返回的DateTimeFormatInfocalled 属性?ShortDateLongTimeFormatShortDatePattern + " " + LongTimePattern


14
我想在Nullable <T>上添加一个名为IsNull的扩展方法,它只会返回!HasValue。IsNull()绝对不如.IsNull漂亮
肯(Ken)

1
我发现这对三?
进制

2
我想用它来模仿Java的enum,它可以具有属性和方法。C#enum不能具有属性或方法,但是可以在它们上创建扩展方法。这个问题对我有用,不应该被关闭。
伊恩·麦克劳德

尽管正如许多人所说的那样,目前尚无计划将其添加到语言中,但没有理由无法完成。F#不仅具有扩展属性,而且对我来说还具有静态扩展,这一事实证明,这至少是一个好主意。
Richiban 2014年

2
应该有一个
Rootel '16

Answers:


364

目前,Roslyn编译器仍不支持该功能……

到目前为止,扩展属性的价值还不足以包含在C#标准的早期版本中。C#7C#8.0将此视为提案的拥护者,但尚未发布,主要是因为即使已经有实现,他们也希望从一开始就做到这一点。

但这会...

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

但是,还有一个更有希望的话题,那就是“扩展所有内容”,重点放在属性和静态类甚至字段上。

此外,您可以使用解决方法

本文所述,您可以使用此TypeDescriptor功能在运行时将属性附加到对象实例。但是,它没有使用标准属性的语法。
它与仅使用语法糖有点不同,因为它可以将扩展属性定义
string Data(this MyClass instance)为扩展方法的别名,
string GetData(this MyClass instance)因为它可以将数据存储到类中。

我希望C#7将为所有功能(属性和字段)提供全功能的扩展,但是在那一点上,只有时间能证明一切。

并随时为社区的未来贡献力量。

更新: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;

与部分类相似,但是在不同的程序集中作为单独的类/类型进行编译。请注意,您还可以通过这种方式添加静态成员和运算符。正如Mads Torgensen播客中提到的那样该扩展名将没有任何状态(因此它无法向该类添加私有实例成员),这意味着您将无法添加链接到该实例的私有实例数据。这样做的原因是它意味着要管理内部词典,并且可能很困难(内存管理等)。为此,您仍然可以使用前面介绍的TypeDescriptor/ ConditionalWeakTable技术,并通过属性扩展将其隐藏在一个不错的属性下。

语法仍然可以更改,这暗示了这个问题。例如,extends可以替换为,for从而使某些人感觉更自然,而与Java的联系较少。

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

扩展并不能将其扩展到C#8.0中,因为在GitHub票证末尾解释了一些缺点。因此,进行了改进设计的探索。在这里,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;
}

57
这是我在StackExchange上遵循的最有用的答案之一。不断更新状态,并让每个人都了解到这一点,提供了到讨论和历史的可靠链接。
bdrelling

25
您保持最新状态真是太好了-谢谢
David Thielen

1
不幸的是,在此评论中,仅为C#11标记了角色,扩展和静态接口成员:(
Ian Kemp,

436

不,它们在C#3.0中不存在,并且不会在4.0中添加。它在C#所需的功能列表中,因此可以在将来的日期添加。

此时,您可以做的最好的就是GetXXX样式扩展方法。


3
与通用属性类似:您必须使用'GetXXX <>'语法。
杰伊·巴祖兹

3
好的,这就是我的想法。@杰伊,是的,我也讨厌那个,呵呵。尤其是无法使用通用索引器... 感叹
Svish

75
链接到所需功能列表?
丹·埃斯帕萨

2
那6.0和7.0版本呢?
Falk

2
截至2020年有任何更新吗?
乍得

265

不,它们不存在。

我知道C#团队在某个时候(或者至少是Eric Lippert)正在考虑使用它们-以及扩展构造函数和运算符(可能需要一些时间才能适应,但是很酷……)但是,我还没有没有任何证据表明它们将成为C#4的一部分。


编辑:它们没有出现在C#5中,并且截至2014年7月,它看起来也不会出现在C#6中。

微软C#编译器团队的首席开发人员埃里克·利珀特Eric Lippert)于2012年11月到2009年10月在博客上发表了以下文章:


2
是的,他们仍然可以隐藏该字段-设置单个属性可以在下面设置两个属性,反之亦然。(想象一下具有正常Size属性和Width / Height扩展属性的东西,反之亦然。)我怀疑它们作为只读的东西会更有用。
乔恩·斯基特

23
您无法绑定到扩展方法...在许多情况下,能够添加自己的属性进行数据绑定可能会有所帮助。
尼克

3
@leppie-我认为,属性扩展的值将使bool和string属性受益最多。()最终消除的可读性要强得多。我个人知道,我编写的扩展名中至少有90%是这两种类型。
代码Maverick

4
为了举例说明为什么这样有用,我有一个EFCF模型。在某些类中,我具有只读属性,可用于返回格式化的信息:FullName= FirstName + LastNameShortName= FirstName + LastName[0]。我想添加更多这些属性,但是我不想“弄脏”实际的类。在这种情况下,扩展属性(只读)是完美的,因为我可以添加功能,保持主类整洁并仍然在UI中公开我想公开的信息。
Gup3rSuR4c

4
@JonSkeet:是的,我最终完成了自己想要的事情,方法是创建自己的类,然后包装所有相关的密封类方法和属性,然后提供static implicit operator FileInfo(FileInfoEx fex)返回包含的FileInfo对象的方法。这实际上使我可以将FileInfoEx视为从FileInfo继承,即使该类是密封的。
史蒂夫L

27

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

Mads Torgersen: “扩展并没有使它进入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功能,不幸的是我没有看到任何功能。
Mateo Torres-Ruiz

1
@ MateoTorres-Ruiz来自“ Mads Torgersen”(C#开发人员)的评论,回答有关此事的人(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

最佳答案
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中将其与一个hacky的ConditionalWeakTable(https://gist.github.com/Jan-WillemdeBruyn/db79dd6fdef7b9845e217958db98c4d4)一起使用


抱歉,尽管看上去很彻底,但它与扩展属性无关,而仅显示扩展方法。
维京人
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.