【问题标题】:What is the precise meaning of MemberInfo.DeclaringType and why is it inconsistent between reflection through expressions and without? [duplicate]MemberInfo.DeclaringType 的确切含义是什么,为什么通过表达式的反射和不通过表达式的反射不一致? [复制]
【发布时间】:2019-10-14 15:57:46
【问题描述】:

提取PropertyInfo 似乎会产生不一致的结果,具体取决于所使用的策略。这是我遇到的问题的一个示例:让我们定义这个简单的类层次结构。

abstract class Top { public abstract int Count { get; set; } }
class BelowTop : Top { public override int Count { get; set; } }

我尝试使用反射提取属性Count

var info1 = typeof(BelowTop).GetProperty("Count");
Console.WriteLine("{0} -> {1}", info1.DeclaringType.Name, info1);
// BelowTop -> Int32 Count

...它告诉我实际声明Count 的类型是具体类BelowTop

如果我现在尝试从表达式中提取此信息:

Expression<Func<BelowTop, int>> lambda = b => b.Count;
var info2 = ((MemberExpression)lambda.Body).Member;
Console.WriteLine("{0} -> {1}", info2.DeclaringType.Name, info2);
// Top -> Int32 Count

...反射突然不同意 DeclaringType 应该是相同的属性。

如果我再添加一层,我会继续得到相同的结果:

class Bottom : BelowTop { }
var info3 = typeof(Bottom).GetProperty("Count");
Console.WriteLine("{0} -> {1}", info3.DeclaringType.Name, info3);
// BelowTop -> Int32 Count
Expression<Func<Bottom, int>> lambda2 = b => b.Count;
var info4 = ((MemberExpression)lambda2.Body).Member;
Console.WriteLine("{0} -> {1}", info4.DeclaringType.Name, info4);
// Top -> Int32 Count

这是一个错误吗?如果不是,这背后的逻辑是什么?如果我不想关心这种差异,如何比较 PropertyInfos?

【问题讨论】:

  • Lambda expression not returning expected MemberInfo 的可能重复项。这是“按设计工作”(尽管意见不同),因为 lambda 中的访问确实是通过 Top 发生的(因为它调用了一个虚拟方法),而直接从 BelowTop 获取属性不会遍历层次结构来查看如果它是一个覆盖 - 该属性是在 BelowTop 上实现的,所以这就是你得到的。

标签: c# reflection


【解决方案1】:

这是一个错误吗?

没有。

如果不是,这背后的逻辑是什么?

对于明确的答案,只有 .NET 类型内省功能(即“反射”)的设计者和/或 C# 的设计者可以说。但是,重要的是要在这里认识到您的两个场景并不完全相同。首先,您已经明确说明了您正在检查的类型。在第二种情况下,您将决定权留给了编译器,它选择了一些与您显然预期不同的东西。

至于编译器为什么这样做,恕我直言,了解这两种情况之间的区别很重要。首先,您要求反射 API 告诉您有关特定派生类型 BelowTop 的信息。它通过声明该成员的新实现来专门覆盖其基类的成员。因此,您所询问的成员实际上是由BelowTop 声明的。

另一方面,第二种情况涉及对给定成员的虚拟调度调用。从编译器的角度来看,唯一重要的是调用虚拟调度表(“v-table”)中的哪个成员。从这个角度来看,感兴趣的成员是在 Top 类中声明的(即,这是实际分配 v-table 条目的类。

毕竟,为什么 lambda 主体要关心传入的 exact 类型的对象呢?它必须BelowTop 实例。它可能是从该类型派生的实例(例如,您的第三个示例中的 Bottom 类型)。相同的虚拟成员将被调用,不管分配虚拟槽的类型是什么,这就是Expression 捕获的类型。

事实上,也许您可​​以看到在Expression 的情况下,如果您的DeclaringType 结果返回BelowTop 会产生多大的误导。如果您实际上使用了带有Bottom 实例的lambda,并且该类型已覆盖Count 属性,则报告的“声明类型”相对于Expression 将不准确。唯一相关的结果是告诉您该 lambda 表达式的行为方式,即它将调用在 Top 中声明的虚拟成员。

当您确切地指定类型时,不会有这种混淆,因为很明显您想知道确切的类型,并且在任何情况下,如果这确实是您想要的,那么无论如何都可以通过您的方式从基类中收集额外的信息。

如果我不想关心这种差异,如何比较 PropertyInfos?

这取决于你所说的“比较PropertyInfos”是什么意思。你想进行什么样的比较?是什么原因?你期待什么结果?

最终,除非您的代码对场景的看法与编译器的看法一致,否则可能很难完成。

例如,虽然反射可以提供有关所编写的原始代码的大量信息,但这是有限度的。例如,它不能告诉您代码中包含的 cmets,也不能告诉您空格的特定排列。在生成 IL 时,编译器不会对这些东西感兴趣,并且在从源代码到 IL 的转换中不会保留这些特性。

同样,当要求编译器从 lambda 构建 Expression 时,它有一项非常具体的工作。该工作是使用 .NET 中的Expression API 来表示如果实际编译了lamba,则生成的代码。因此,除非您的代码对 lambda 的期望视图与此目标一致,即您愿意只关注与 IL 的代码生成相关的重要事项,否则检查代表 lambda 的 Expression 可能不会让你达到你想达到的任何目标。在这种情况下,信息可能根本不存在。

也就是说,如果您的目标可以通过简单地确保调用相同的虚拟成员来实现,而不管哪个类已覆盖它,那么在第一种情况下,即您从哪里获取成员信息明确声明Type 对象,您可以按照自己的方式备份继承链以找到 实际 声明类型,并将其与您从表达式对象中检索到的成员信息进行比较。

或者,如果可以只关心表达式的 参数 类型,您可以从 Expression 对象而不是表达式的 Body 访问它。


最后,我会注意到,与您询问的情况相比,您的示例有点复杂。一个字段或方法,因为属性和反射如何交互。特别是,因为一个属性实际上是两个独立的方法,所以反射可能会返回看似冲突的信息,具体取决于您查看成员的 PropertyInfo 还是 MethodInfo

有关该特定方面的更多信息,您可能会发现这些问答对阅读有帮助:
Why does sealed override of a property in C# copy not overriden accessor from base type?
Why do CanRead and CanWrite return false in C# for properties with overridden accessors?

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-04-30
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多