【问题标题】:Distinguish between getter-only property and expression body property?区分 getter-only 属性和表达式主体属性?
【发布时间】:2020-06-23 12:37:06
【问题描述】:

是否可以使用反射来区分 getter-only 属性和表达式主体属性?

class MyClass
{
    DateTime GetterOnly { get; }

    DateTime ExpressionBody => DateTime.Now;
}

例如,下面的方法如何完成?

enum PropertyKind
{
    NotInteresting,

    GetterOnly,

    ExpressionBody,
}

PropertyKind GetPropertyKind(PropertyInfo propertyInfo)
{
    if (propertyInfo.GetSetMethod(true) == null)
    {
        // what goes here??
    }

    return PropertyKind.NotInteresting;
}

相关文章: What is the difference between getter-only auto properties and expression body properties?

【问题讨论】:

  • 你当然可以通过反射判断是否只实现了一个getter(只需搜索setter,它就不会在那里)。
  • 一个表达式体属性一个只有getter的属性,它的getter不只是返回支持字段值
  • 我怀疑真正的问题是不同的。您是否正在尝试创建自定义序列化程序或分析器? Roslyn 分析器可以检测语法是仅获取的自动属性还是表达式体的。这就是重构可以在两者之间切换的方式。但在运行时,一个 getter 和另一个 getter 之间没有区别,除了它们的代码。反射无法分辨这些吸气剂来自哪里
  • 当您说“表达式主体属性”时,您实际上是指那个意思,还是真的是指“自动实现的属性”?因为从 IL 中您无法区分普通属性和表达式主体属性。
  • 除了一个属性和支持字段的存在:this sharplab.io example 表示自动属性具有CompilerGenerated 属性

标签: c# .net reflection .net-4.0 c#-7.0


【解决方案1】:

首先我们必须定义我们的术语:

  • 自动属性 ​​- 具有由编译器自动生成的支持字段。
  • Expression-bodied 属性 - 一种使用 => (lambda) 语法实现的属性。
  • 函数体属性 - 一种使用普通 {...} 语法实现的属性。

请务必注意,无法区分表达式体属性和函数体属性,因为实际上会为两者生成相同的 IL。

但是,我相信您真正想要的是能够区分自动属性和非自动属性。

这是可能的,因为编译器会生成一个以[CompilerGeneratedAttribute] 修饰的支持字段,并且该字段具有派生自该属性的名称,可以对其进行测试。

支持字段名称当前始终为“k__BackingField”(其中PropertyName 是属性名称),对于 .Net 4.6 和 .Net Core 3.1 都是如此 - 当然这是在无法保证永远不会更改,因此任何依赖于此的代码都可能在 C# 编译器的未来版本中中断。

尽管有这么大的警告,您可以编写一个方法来检查 PropertyInfo 是否实现了这样的自动属性:

public static bool IsAutoProperty(PropertyInfo property)
{
    string backingFieldName = $"<{property.Name}>k__BackingField";
    var    backingField     = property.DeclaringType.GetField(backingFieldName, BindingFlags.NonPublic | BindingFlags.Instance);

    return backingField != null && backingField.GetCustomAttribute(typeof(CompilerGeneratedAttribute)) != null;
}

这将检查属性以查看 (a) 它是否具有从属性名称派生的特定名称的支持字段,以及 (b) 该支持字段是编译器生成的。

我认为这不是一个好主意,因为它依赖于未记录和凭经验确定的编译器行为,因此需要谨慎!

这是一个可编译的控制台应用程序来演示:

using System;
using System.Reflection;
using System.Runtime.CompilerServices;

namespace ConsoleApplication1
{
    static class Program
    {
        static void Main(string[] args)
        {
            var type = typeof(MyClass);

            foreach (var property in type.GetProperties(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance))
            {
                if (IsAutoProperty(property))
                    Console.WriteLine($"{property.Name} is an auto-property");
            }
        }

        public static bool IsAutoProperty(PropertyInfo property)
        {
            string backingFieldName = $"<{property.Name}>k__BackingField";
            var    backingField     = property.DeclaringType.GetField(backingFieldName, BindingFlags.NonPublic | BindingFlags.Instance);

            return backingField != null && backingField.GetCustomAttribute(typeof(CompilerGeneratedAttribute)) != null;
        }
    }

    class MyClass
    {
        DateTime GetterOnly { get; }

        DateTime ExpressionBody => DateTime.Now;
    }
}                                                                                                 

这个输出:

GetterOnly 是一个自动属性

【讨论】:

  • 我很欣赏这篇文章的细节。我有一篇很长的文章作为对我的问题的评论,但没有提交。你给了我很多思考。这里的部分复杂性是有很多方法可以创建一个属性,但是我的开发人员并没有使用这些选项的整个领域。我真正想要了解的是区分需要序列化的属性,因为它们对于再水化对象和不需要序列化的对象是必要的,因为它们是其他信息的派生。
  • 我认为这通常是一个很难解决的问题,因此我使用我的开发人员仅以某些方式编写属性的事实作为代理来确定序列化需要哪些属性,哪些不需要.
  • @SFun28 您能否创建自己的自定义属性类型并要求您的开发人员用它装饰适当的属性?然后您可以使用反射来查看属性是否具有该属性。
  • 我不喜欢属性的属性。我觉得它污染了模型。无论如何,这足以让我继续前进!
  • @SFun28 我怀疑只检查CompilerGeneratedAttribute就足够了。
猜你喜欢
  • 2015-03-10
  • 1970-01-01
  • 1970-01-01
  • 2020-06-16
  • 2015-12-05
  • 2020-12-03
  • 1970-01-01
  • 1970-01-01
  • 2019-03-30
相关资源
最近更新 更多