如何让孩子从表达式中声明类型?

我有一个Parent / Child类层次结构,其中Parent抽象地声明了一个字符串属性,而Child类实现了它:

abstract class Parent { public abstract string Value { get; } } class Child : Parent { public override string Value { get { return null; } } } 

当我使用显式(或隐式)使用Child类的表达式时,我希望Expressions的MemberInfo的DeclaringType为’Child’,但它是Parent:

 Child child = new Child(); Expression<Func> expression = (() => child.Value); MemberInfo memberInfo = expression.GetMemberInfo(); Assert.AreEqual(typeof(Child), memberInfo.DeclaringType); // FAILS! 

断言失败,因为DeclaringType是Parent。

在声明我的表达或使用它来揭示Child类型的实际用途时,我能做些什么吗?

注意:上面的GetMemberInfo()作为扩展方法(我甚至忘记了我们写过这个!):

 public static class TypeExtensions { ///  /// Gets the member info represented by an expression. ///  /// The member expression. /// The member info represeted by the expression. public static MemberInfo GetMemberInfo(this Expression expression) { var lambda = (LambdaExpression)expression; MemberExpression memberExpression; if (lambda.Body is UnaryExpression) { var unaryExpression = (UnaryExpression)lambda.Body; memberExpression = (MemberExpression)unaryExpression.Operand; } else memberExpression = (MemberExpression)lambda.Body; return memberExpression.Member; } } 

否 – 这是C#编译器发出的内容的准确表示。 在查找成员时,有效地忽略了覆盖 – 编译器只关心最初声明成员的类型。 您可以通过编译代码然后查看IL来亲自看到这一点。 这个方法:

 static void Main() { Child c = new Child(); string x = c.Value; } 

编译成这个IL:

 IL_0000: nop IL_0001: newobj instance void Child::.ctor() IL_0006: stloc.0 IL_0007: ldloc.0 IL_0008: callvirt instance string Parent::get_Value() IL_000d: stloc.1 IL_000e: ret 

琐事的一点:VB编译器的工作方式不同,所以这个方法:

 Public Shared Sub Main(Args As String()) Dim x As Child = New Child() Dim y As String = x.Value End Sub 

编译为:

 IL_0000: newobj instance void [lib]Child::.ctor() IL_0005: stloc.0 IL_0006: ldloc.0 IL_0007: callvirt instance string [lib]Child::get_Value() IL_000c: stloc.1 IL_000d: ret 

我的解决方案,基于来自@JonSkeet和@CodeInChaos的信息,不是纯粹看表达式中的PropertyInfo,而是看看MemberExpression的Member组件的类型:

 ///  /// Extracts the PropertyInfo for the propertybeing accessed in the given expression. ///  ///  /// If possible, the actual owning type of the property is used, rather than the declaring class (so if "x" in "() => x.Foo" is a subclass overriding "Foo", then x's PropertyInfo for "Foo" is returned rather than the declaring base class's PropertyInfo for "Foo"). ///  ///  ///  ///  internal static PropertyInfo ExtractPropertyInfo(Expression> propertyExpression) { if (propertyExpression == null) { throw new ArgumentNullException("propertyExpression"); } var memberExpression = propertyExpression.Body as MemberExpression; if (memberExpression == null) { throw new ArgumentException(string.Format("Expression not a MemberExpresssion: {0}", propertyExpression), "propertyExpression"); } var property = memberExpression.Member as PropertyInfo; if (property == null) { throw new ArgumentException(string.Format("Expression not a Property: {0}", propertyExpression), "propertyExpression"); } var getMethod = property.GetGetMethod(true); if (getMethod.IsStatic) { throw new ArgumentException(string.Format("Expression cannot be static: {0}", propertyExpression), "propertyExpression"); } Type realType = memberExpression.Expression.Type; if(realType == null) throw new ArgumentException(string.Format("Expression has no DeclaringType: {0}", propertyExpression), "propertyExpression"); return realType.GetProperty(property.Name); } 

如果你不想使用静态类型的方法,而是最新的覆盖,那么它是可能的。 我没有测试,但类似于以下的东西应该做的工作:

 public bool FindOverride(MethodInfo baseMethod, Type type) { if(baseMethod==null) throw new ArgumentNullException("baseMethod"); if(type==null) throw new ArgumentNullException("type"); if(!type.IsSubclassOf(baseMethod.ReflectedType)) throw new ArgumentException(string.Format("Type must be subtype of {0}",baseMethod.DeclaringType)); while(true) { var methods=type.GetMethods(BindingFlags.Instance| BindingFlags.DeclaredOnly| BindingFlags.Public| BindingFlags.NonPublic); var method=methods.FirstOrDefault(m=>m.GetBaseDefinition()==baseMethod)) if(method!=null) return method; type=type.BaseType; } } 

MemberInfo作为第一个参数传递,将对象的运行时类型作为第二个参数传递的位置。 请注意,这可能很慢,因此您可能需要添加一些缓存。