C#是否具有扩展属性?

Does C# have extension properties?

C是否具有扩展属性?

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


不,它们不存在于C 3.0中,也不会添加到4.0中。它在C的功能需求列表中,因此它可能会在将来添加。

此时,您所能做的最好的事情就是getxxx样式的扩展方法。


不,它们不存在。

我知道C团队曾经考虑过他们(或者至少Eric Lippert曾经考虑过),以及扩展构造函数和操作员(这些可能需要一段时间才能让你头脑清醒,但是很酷…)但是,我没有看到任何证据表明他们将是C 4的一部分。

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

Eric Lippert,微软C编译器团队的主要开发人员,在2012年11月之前,在2009年10月的博客中写到:

  • 为什么没有扩展属性?–在编码方面的惊人冒险


目前,Roslyn编译器仍然不支持它。

直到现在,扩展属性还没有被认为有足够的价值,不足以包含在C标准的早期版本中。C 7和C 8.0将其视为提案的拥护者,但它尚未发布,最重要的是,即使已经有了一个实现,他们也希望从一开始就实现它。

但它会…

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

然而,还有一个更具前景的主题是"扩展一切",重点放在属性、静态类甚至字段上。

此外,您还可以使用变通方法

如本文所述,您可以使用TypeDescriptor功能在运行时将属性附加到对象实例。但是,它没有使用标准属性的语法。
它与简单的语法结构稍有不同,因为它可以在类中存储数据时,将扩展属性(如
EDOCX1[1])定义为扩展方法
EDOCX1[2]的别名。

我希望C 7将提供一个完整的功能扩展,所有的东西(属性和字段),但是在这一点上,只有时间可以证明。

因为明天的软件将来自社区,所以您可以自由地做出贡献。

更新日期:2016年8月

正如Dotnet团队在C 7.0和Mads Torgensen的评论中发布的一样:

Extension properties: we had a (brilliant!) intern implement them over
the summer as an experiment, along with other kinds of extension
members. We remain interested in this, but it’s a big change and we
need to feel confident that it’s worth it.

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

更新日期:2017年5月

扩展成员已作为扩展的副本关闭,所有问题也已关闭。主要讨论的实际上是广义上的类型可扩展性。此功能现在作为建议在此处跟踪,已从7.0里程碑中删除。

更新:2017年8月-C 8.0提议功能

虽然它仍然只是一个提议的特性,但我们现在对它的语法有了更清晰的了解。请记住,这也是扩展方法的新语法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
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技术,通过属性扩展,将其隐藏在一个不错的属性下。

语法仍有可能发生变化,这意味着这个问题。例如,EDCOX1的5度可以被EDCOX1(6)所代替,一些EDCOX1可能更自然,更不相关。

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

由于一些被解释为GitHub票据末尾的缺点,扩展的所有内容都没有到达C 8.0。因此,对改进设计进行了探索。在这里,Mads Torgensen解释了什么是角色和扩展,以及它们的区别:

Roles allow interfaces to be implemented on specific values of a given
type. Extensions allow interfaces to be implemented on all values of a
given type, within a specific region of code.

在两个用例中,可以在先前建议的一个部分看到它。扩展的新语法如下:

1
2
3
4
5
6
7
8
9
10
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));
        }
    }
}

那么你就可以这样做了:

1
2
3
4
foreach (byte b in 0x_3A_9E_F1_C5_DA_F7_30_16ul)
{
    WriteLine($"{e.Current:X}");
}

对于静态接口:

1
2
3
4
5
public interface IMonoid<T> where T : IMonoid<T>
{
    static T operator +(T t1, T t2);
    static T Zero { get; }
}

int上增加一个扩展属性,将int作为IMonoid处理:

1
2
3
4
public extension IntMonoid of int : IMonoid<int>
{
    public static int Zero => 0;
}


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

Mads Torgersen:"Extension everything didn’t make it into C# 8.0. It got"caught up", if you will, in a very exciting debate about the further future of the language, and now we want to make sure we don’t add it in a way that inhibits those future possibilities. Sometimes language design is a very long game!"

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

我不再计算这些年来我打开这个问题的次数,希望看到这个问题得以实现。

好吧,我们终于可以欢呼雀跃了!微软将在他们即将发布的C 8版本中对此进行介绍。

所以与其这样做…

1
2
3
4
5
6
7
public static class IntExtensions
{
&nbsp;&nbsp;&nbsp;public static bool Even(this int value)
&nbsp;&nbsp;&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return value % 2 == 0;
&nbsp;&nbsp;&nbsp;}
}

我们终于可以这样做了…

1
2
3
4
public extension IntExtension extends int
{
    public bool Even => this % 2 == 0;
}

资料来源:https://blog.ndepend.com/c-8-0-features-seven-future/


如@psyonity所述,您可以使用ConditionalWeakTable向现有对象添加属性。结合动态Expandoobject,您可以在几行中实现动态扩展属性:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
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注释中有一个用法示例:

1
2
3
4
5
6
7
8
9
10
11
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

因为我最近需要这个,我在下面的文章中研究了答案的来源:

通过添加属性扩展类

并创建了一个更动态的版本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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)