通过反射检查属性是否为计算属性

4
请想象一个类中有以下两个属性:
```html

请想象一个类中有以下两个属性:

```
public string Category { get; set; }
public string DisplayCategory => "SomeCategory"

现在我只想收集所有的PropertyInfo对象,其中属性本身不是计算出来的。
var properties = type.GetProperties();
var serializables = properties.Where(p => p.CanRead, true));

如何通过Reflection找出属性是否为计算属性,以便忽略它?
我这样做的原因是,我使用Expression Trees自动创建查询,并通过Entity Framework 6进行处理。Entity Framework仅为非计算属性创建列,因此无法查询计算属性。
参见本文

4
“computed” 的准确定义是什么?是否指除了编译时常量以外的任何内容?如果读取一个静态只读字段,它不是常量,但仅被初始化一次,这是否算作“computed”呢? - Jon Skeet
每个getter都有一些逻辑,而不是像字段一样行为。我只想要那些可以由“Entity Framework”映射并可以使用“Expression Framework”查询的剩余属性。 - Sebastian Krogull
5
“某种逻辑”是指什么?如果两个属性都检索相同的字段,即使它们反映了单个状态片段,是否算作非计算属性?如果getter只读取字段,但setter执行验证,那怎么办?如果getter读取一个字段然后从中获取一个属性,例如 public int FooLength => foo.Length;呢?如果属性只是读取另一个属性而不是一个字段呢?这里有各种微妙之处... - Jon Skeet
为什么EF不能处理具有“复杂”主体的属性?我猜只要它是一个属性(最终装饰有属性,我不是EF的专家),EF就可以处理它,不是吗? - MakePeaceGreatAgain
应该匹配这两个属性中的哪一个?如果两个都可以,请给出一个不应该被匹配的属性的示例。 - MakePeaceGreatAgain
我只想要第一个,第二个应该被排除。 - Sebastian Krogull
2个回答

2
以下方法对我有效。我试图查找计算属性和没有Set方法的属性之间的区别:
public int Computed => 2;
public int NoSet { get; }

PropertyInfo.SetMethod 对于两者都返回 null。

查看 get 方法生成的 IL,NoSet 属性上有 CompilerGeneratedAttribute,但 Computed 属性上没有:

.method public hidebysig specialname instance int32
  get_Computed() cil managed
{
  .maxstack 8

  // [215 32 - 215 33]
  IL_0000: ldc.i4.2
  IL_0001: ret
}

.method public hidebysig specialname instance int32
  get_NoSet() cil managed
{
  .custom instance void [System.Runtime]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor()
    = (01 00 00 00 )
  .maxstack 8

  // [217 28 - 217 32]
  IL_0000: ldarg.0      // this
  IL_0001: ldfld        int32 TestNamespace.TestClass::'<NoSet>k__BackingField'
  IL_0006: ret

}

因此,检查变为:
if (property.SetMethod == null && property.GetMethod.GetCustomAttribute(typeof(CompilerGeneratedAttribute)) == null)
    return "Computed Property";

1
我使用DelegateDecompiler,目前我需要手动将每个计算属性名称添加到自定义配置中。
如果下面的ShouldDecompile方法可以自动确定应该反编译哪些属性,那就太好了:
  public class CustomDelegateDecompilerConfiguration : Configuration {
    public static CustomDelegateDecompilerConfiguration Instance { get; } = new CustomDelegateDecompilerConfiguration();
    public static void Enable() => Configuration.Configure(Instance);

    public CustomDelegateDecompilerConfiguration() {
      RegisterDecompileableMember<Person, string>(x => x.Name);

      RegisterDecompileableMembers(typeof(string), nameof(string.IsNullOrWhiteSpace));

      RegisterDecompileableMembers(typeof(CustomComputedMethods), new[] {
        nameof(CustomComputedMethods.PersonName),
        nameof(CustomComputedMethods.MonthInteger),
        nameof(CustomComputedMethods.WholeMonthsBetween),
        nameof(CustomComputedMethods.WholeYearsBetween)
      });

    }

    public HashSet<MemberInfo> DecompileableMembers { get; } = new HashSet<MemberInfo>();

    public override bool ShouldDecompile(MemberInfo memberInfo) => memberInfo.GetCustomAttributes(typeof(DecompileAttribute), true).Length > 0
      || memberInfo.GetCustomAttributes(typeof(ComputedAttribute), true).Length > 0
      || memberInfo.GetCustomAttributes(typeof(CompilerGeneratedAttribute), true).Length > 0
      || DecompileableMembers.Contains(memberInfo)
      //***  Would be nice if auto detection was possible with non-existing methods below ***
      //|| memberInfo.AutoProperty      (One with a backing field automatically generated by the compiler)
      //|| memberInfo.HasExpressionBody (One implemented using the => (lambda) syntax)
      //|| memberInfo.HasFunctionBody   (One implemented using the normal {...} syntax)
      ;

    public override void RegisterDecompileableMember(MemberInfo prop) => DecompileableMembers.Add(prop);

    public void RegisterDecompileableMember<T, TResult>(Expression<Func<T, TResult>> expression) where T : class => RegisterDecompileableMember(expression.Body.GetMemberInfo());

    public void RegisterDecompileableMembers(Type type, params string[] memberNames) {
      foreach(var tmi in type.GetMembers().Where(mi => memberNames.Contains(mi.Name))) {
        DecompileableMembers.Add(tmi);
      }
    }

    public void RegisterDecompileableMembers<T>(params string[] memberNames) where T : class => RegisterDecompileableMembers(typeof(T), memberNames);

  }

一个示例类:

  public class Person {
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string AlternativeFirstName { get; set; }

    public string Name => string.Concat(AlternativeFirstName == string.Empty ? FirstName : AlternativeFirstName, " ", LastName);
  }

一些示例扩展方法:

  public static class CustomComputedMethods {
    public static string PersonName(string firstName, string lastName, string knownAs) => (knownAs ?? firstName).Trim() + " " + lastName.Trim();
    public static long MonthInteger(this DateTime d) => checked(d.Year * 12 + d.Month);
    public static int WholeMonthsBetween(this DateTime d, DateTime maxDate) => (int)(maxDate.MonthInteger() - d.MonthInteger() - (d.Day > maxDate.Day ? 1 : 0));
    public static int WholeYearsBetween(this DateTime d, DateTime maxDate) => d.WholeMonthsBetween(maxDate) / 12;
  }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接