簡體   English   中英

C# 動態類型問題

[英]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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM