[英]C# dynamic type gotcha
我剛剛遇到了最奇怪的事情,我有點介意=此刻...
下面的程序編譯得很好,但是當你運行它時,當你嘗試讀取Value
時,你會得到一個RuntimeBinderException
。 'object' does not contain a definition for 'Value'
class Program
{
interface IContainer
{
int Value { get; }
}
class Factory
{
class Empty : IContainer
{
public int Value
{
get { return 0; }
}
}
static IContainer nullObj = new Empty();
public IContainer GetContainer()
{
return nullObj;
}
}
static void Main(string[] args)
{
dynamic factory = new Factory();
dynamic container = factory.GetContainer();
var num0 = container.Value; // WTF!? RuntimeBinderException, really?
}
}
這是令人興奮的部分。 將嵌套類型Factory+Empty
Factory
類之外,如下所示:
class Empty : IContainer
{
public int Value
{
get { return 0; }
}
}
class Factory...
程序運行得很好,有人願意解釋這是為什么嗎?
在我的編碼冒險中,我當然做了一些我應該首先考慮的事情。 這就是為什么你會看到我對 class private 和 internal 之間的區別漫不經心。 這是因為我設置了InternalsVisibleToAttribute
,它使我的測試項目(在本例中消耗了比特)按照他們的方式運行,這完全是設計的,盡管從一開始就暗指我。
閱讀 Eric Lippert 的回答,以獲得對其余部分的一個很好的解釋。
真正讓我感到警惕的是,動態綁定器考慮了實例類型的可見性。 我有很多 JavaScript 經驗,作為一個 JavaScript 程序員,其中真的沒有公共或私有之類的東西,我完全被可見性很重要的事實所迷惑,我的意思是畢竟,我正在訪問這個成員,好像它是公共接口類型(我認為動態只是反射的語法糖)但動態綁定器不能做出這樣的假設,除非你給它一個提示,使用簡單的轉換。
C# 中“動態”的基本原則是:在運行時對表達式進行類型分析,就好像運行時類型是編譯時類型一樣。 那么讓我們看看如果我們真的這樣做會發生什么:
dynamic num0 = ((Program.Factory.Empty)container).Value;
該程序將失敗,因為Empty
不可訪問。 dynamic
不允許您進行一開始就非法的分析。
然而,運行時分析器意識到了這一點並決定作弊。 它問自己“是否有可訪問的 Empty 基類?” 答案顯然是肯定的。 所以它決定回退到基類並分析:
dynamic num0 = ((System.Object)container).Value;
失敗是因為該程序會給您一個“對象沒有名為 Value 的成員”錯誤。 這是你得到的錯誤。
動態分析永遠不會說“哦,你一定是這個意思”
dynamic num0 = ((Program.IContainer)container).Value;
因為當然,如果這就是您的意思,那您一開始就會這么寫。 同樣, dynamic
的目的是回答如果編譯器知道運行時類型會發生什么的問題,並且轉換到接口不會給你運行時類型。
當您將Empty
移到外面時,動態運行時分析器會假裝您寫道:
dynamic num0 = ((Empty)container).Value;
現在Empty
可以訪問並且強制轉換是合法的,所以你得到了預期的結果。
更新:
可以將該代碼編譯為程序集,引用此程序集,如果 Empty 類型在類之外,默認情況下將使其成為內部類型,它將起作用
我無法重現所描述的行為。 讓我們嘗試一個小例子:
public class Factory
{
public static Thing Create()
{
return new InternalThing();
}
}
public abstract class Thing {}
internal class InternalThing : Thing
{
public int Value {get; set;}
}
> csc /t:library bar.cs
class P
{
static void Main ()
{
System.Console.WriteLine(((dynamic)(Factory.Create())).Value);
}
}
> csc foo.cs /r:bar.dll
> foo
Unhandled Exception: Microsoft.CSharp.RuntimeBinder.RuntimeBinderException:
'Thing' does not contain a definition for 'Value'
你會看到它是如何工作的:運行時綁定器檢測到InternalThing
是外部程序集的內部,因此在 foo.exe 中無法訪問。 所以它回退到公共基類型Thing
,它可以訪問但沒有必要的屬性。
我無法重現您描述的行為,如果您可以重現它,那么您就發現了一個錯誤。 如果您有該錯誤的小副本,我很樂意將其傳遞給我以前的同事。
我猜,在運行時,容器方法調用只是在私有 Empty 類中解析,這會使您的代碼失敗。 據我所知,動態不能用於訪問私有成員(或私有類的公共成員)
這應該(當然)工作:
var num0 = ((IContainer)container).Value;
在這里,它是私有的 Empty 類:因此您不能在聲明類(工廠)之外操作 Empty 實例。 這就是您的代碼失敗的原因。
如果 Empty 是內部的,您將能夠在整個程序集中操作它的實例(好吧,並不是因為 Factory 是私有的)允許所有動態調用,並且您的代碼可以工作。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.