很久以前我写过一篇文章简单介绍扩展方法、接口和继承带来的有趣现象,而这篇文章就没那么“有趣”了,介绍由于扩展方法和静态方法命名的冲突引起一个莫名其妙的错误,由于这个莫名其妙的错误暂时使我不能以较好的方式实现我的一些想法,特郁闷的是我觉得不应该是编程上的错误,而且本来就不应该有这种错误,所以称之为莫名其妙的错误。
错误描述:Member '...TestMethod()' cannot be accessed with an instance reference; qualify it with a type name instead
是编译错误,其大概意思是实例不能访问TestMethod这个方法,请用类型名字代替。即编译器认为我是用实例访问一个静态方法,而实际上该实例是访问扩展方法,只是这个类的静态方法和扩展方法名字都是叫TestMethod。
有人可能会问:你没事干嘛在静态方法和扩展方法中都用同一个名字?
这一切从我开始为Expression写扩展方法说起。这里给段小插曲——近来写了不少与Expression有关的扩展和封装,以挖掘Expression更多的应用。写多了,开始厌倦了这种静态方法:
Expression.Constant(...) ; Expression.Property (...); Expression.Lambda(...);
如果在实际中想构建以下Lambda表达式,
Expression<Func<Foo, bool>> expr = c => c.属性==1;
那么参数、属性、lambda、二元表达式等等是不可避免的,如下伪代码:
... Expression.Parameter (typeof(Foo), "c"); ... Expression.Property (...); ... Expression.Equal(...); ... Expression.Lambda(...); ...
如果要构建的表达式比较长的话,我觉得以上代码对于阅读者来说,当他想看到各表达式之间的联系是有点困难的。
于是我试图对常用表达式进行扩展来达到类似代码风格:
ParameterExpression c= typeof(Foo).AsParameter("c"); Expression<Func<Foo, bool>> expr = c.Lambda(c.Property("属性").Equal(1));
因为这种写法跟它本身要实现的目标基本上是比较接近,如: c.Lambda(c.Property("属性").Equal(1)) 是实现 c => c.属性==1
我感觉这种写法更整洁,语言也清晰,所以立马付诸行动,结果碰了一鼻子灰。
原因是无法调用这个扩展方法:
public static MemberExpression Property(this Expression expression, string propertyName)
Why?
因为 Expression 里有这一个静态方法:
public static MemberExpression Property(Expression expression, string propertyName)
这个原因让我哭笑不得,因为我认为编译器应该可以区分两者,如:
Expression expr = ...; expr.Property("属性"); //使用扩展方法 Expression.Property("属性"); //使用静态方法
就是不明白它为什么死活不让我编译通过,同样代码在VS2010也是报同样错误,因此以上对表达式扩展的构思暂时搁置一边(当然改另一个扩展方法名字就可以编译通过了,不过暂时想弄明白为什么会出现这种错误)。
为了进一步看清问题的根源,我特意写了以下代码测试:
public class A { public static void Method() { } }
public static class AExt { public static void Method(this A a) { } }
static void Main(string[] args) { A a = new A(); a.Method(); }
以上代码可以编译成功吗?答案是:不成功!除非我们不使用那个与静态方法相同签名的扩展方法,即如果没有 a.Method(); 这一句,代码是可以编译通过的。
同样相应的派生类也是无法使用该同名扩展方法,如
public class B:A { } public static class BExt { public static void Method(this B b) { } }
都会因为A里面的静态方法,导致无法使用与静态方法相同签名的扩展方法。但是尚未发现有什么资料提到这个扩展方法限制,更没有找到相关解析。
对此,我觉得与之类似的是:一个类无法包含两个相同签名的方法,即使一个是状态,另一个非静态。如
public class A { public static void Method() { } public void Method() { } }
但是令我疑惑不解的是,扩展方法虽然在使用上让人感觉是“调用”类里面的public方法,但归根到底它不属于扩展的类,而且编译器也没有这样子做,typeof(...).GetMethod(...) 也不会反射出扩展方法。既然静态方法和扩展方法隶属于不同类,调用形式也不一样,为什么不可以使用相同签名?是限制还是编译器的bug?
总结
截至本文发表日,我仍然对这个莫名其妙的错误抱怀疑态度,还请其他知情人帮忙分析。(注:如有结论会第一时间更新此文)