19

私はちょうど奇妙なことに遭遇しました、そして私は少し気になっています=現時点で吹き飛ばされています...

次のプログラムは正常にコンパイルされますが、実行するRuntimeBinderExceptionと、を読み込もうとするとが表示されますValue'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...

そして、プログラムは問題なく実行されます、誰もがそれがなぜであるかを説明するのを気にしますか?

編集

コーディングの冒険では、もちろん、最初に考えるべきことをしました。そのため、私がクラスprivateとinternalの違いについて少しとりとめのないように見えます。これはInternalsVisibleToAttribute、最初からほのめかしているものの、テストプロジェクト(この場合はビットを消費していた)を設計どおりに動作させるように設定したためです。

残りの良い説明については、EricLippertの回答を読んでください。

私が本当に警戒したのは、動的バインダーがインスタンスのタイプの可視性を念頭に置いていることでした。私はJavaScriptの経験が豊富で、パブリックやプライベートのようなものが実際にはないJavaScriptプログラマーとして、可視性が重要であるという事実に完全にだまされました。つまり、私はこのメンバーにアクセスしているかのようにアクセスしていました。これはパブリックインターフェイスタイプでしたが(動的は単にリフレクションの構文糖衣だと思いました)、動的バインダーは、単純なキャストを使用してヒントを与えない限り、そのような仮定を行うことはできません。

4

2 に答える 2

15

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ます。これはアクセス可能ですが、必要なプロパティがありません。

私はあなたが説明した振る舞いを再現することができません、そしてあなたがそれを再現することができればあなたはバグを見つけました。バグの小さな再現があれば、以前の同僚に喜んで伝えます。

于 2013-03-11T14:53:32.763 に答える
2

実行時に、コンテナメソッドの呼び出しはプライベートEmptyクラスで解決されるだけなので、コードは失敗します。私の知る限り、ダイナミックを使用してプライベートメンバー(またはプライベートクラスのパブリックメンバー)にアクセスすることはできません

これは(もちろん)機能するはずです:

var num0 = ((IContainer)container).Value;

ここでは、プライベートなクラスEmptyです。したがって、宣言しているクラス(ファクトリ)の外部でEmptyインスタンスを操作することはできません。そのため、コードは失敗します。

Emptyが内部にある場合は、アセンブリ全体でインスタンスを操作して(Factoryがプライベートであるため、実際にはそうではありません)、すべての動的呼び出しを許可し、コードを機能させることができます。

于 2013-03-11T12:57:44.623 に答える