很久以前我寫過一篇文章簡單介紹擴充方法、介面和繼承帶來的有趣現象,而這篇文章就沒那麼“有趣”了,介紹由於擴充方法和靜態方法命名的衝突引起一個莫名其妙的錯誤,由於這個莫名其妙的錯誤暫時使我不能以較好的方式實現我的一些想法,特鬱悶的是我覺得不應該是編程上的錯誤,而且本來就不應該有這種錯誤,所以稱之為莫名其妙的錯誤。
錯誤描述: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?
總結
截至本文發表日,我仍然對這個莫名其妙的錯誤抱懷疑態度,還請其他知情人幫忙分析。(註:如有結論會第一時間更新此文)