15

私の質問はこれに多少関連しています:ジェネリック制約は、暗黙的に実装されたインターフェイスを使用した値型のボックス化をどのように防止しますか? 、しかし、これはまったく一般的ではないため、これを行うために制約を必要としないため、異なります。

私はコードを持っています

interface I { void F(); }
struct C : I { void I.F() {} }
static class P {
    static void Main()
    {    
        C x;
        ((I)x).F();
    }
}

main メソッドは次のようにコンパイルされます。

IL_0000:  ldloc.0
IL_0001:  box        C
IL_0006:  callvirt   instance void I::F()
IL_000b:  ret

なぜこれにコンパイルされないのですか?

IL_0000:  ldloca.s   V_0
IL_0002:  call       instance void C::I.F()
IL_0007:  ret

仮想呼び出しを行うためにメソッド テーブルが必要な理由はわかりますが、この場合は仮想呼び出しを行う必要はありません。インターフェイスが正常に実装されている場合、仮想呼び出しは行われません。

関連:明示的なインターフェイスの実装がプライベートなのはなぜですか? - この質問に対する既存の回答では、メソッドがメタデータで非公開としてマークされている理由を適切に説明していません (単に使用できない名前を持つのではなく)。しかし、これでもボックス化されている理由を完全には説明できません。なぜなら、C の内部から呼び出されたときにまだボックス化されているからです。

4

3 に答える 3

8

答えは、インターフェイスをどのように扱うことができるかについての C# 仕様にあると思います。仕様から:

C# には、フィールド、配列要素、ローカル変数、パラメーターなど、いくつかの種類の変数があります。変数は格納場所を表し、次の表に示すように、すべての変数には変数に格納できる値を決定する型があります。

次の表の下に、インターフェースについて記載されています

null 参照、そのインターフェイス型を実装するクラス型のインスタンスへの参照、またはそのインターフェイス型を実装する値型のボックス化された値への参照

値型のボックス化された値になることを明示的に示しています。コンパイラは仕様に従っているだけです

** 編集 **

コメントに基づいてさらに情報を追加します。同じ効果がある場合、コンパイラは自由に書き直すことができますが、ボックス化が発生するため、値の型のコピーが同じ値の型を持たないようにします。再び仕様から:

ボックス化変換は、ボックス化された値のコピーを作成することを意味します。これは、参照型から型オブジェクトへの変換とは異なります。この変換では、値は引き続き同じインスタンスを参照し、派生度の低い型オブジェクトと見なされます。

これは、毎回ボクシングを行う必要があることを意味します。そうしないと、一貫性のない動作が発生します。この簡単な例は、提供されたプログラムで次のように実行することで表示できます。

public interface I { void F(); }
public struct C : I {
    public int i;
    public void F() { i++; } 
    public int GetI() { return i; }
}

    class P
    {
    static void Main(string[] args)
    {
        C x = new C();
        I ix = (I)x;
        ix.F();
        ix.F();
        x.F();
        ((I)x).F();
        Console.WriteLine(x.GetI());
        Console.WriteLine(((C)ix).GetI());
        Console.ReadLine();
    }
}

そのオブジェクトで呼び出されるCたびに 1 ずつインクリメントされる構造体に内部メンバーを追加しました。F()これにより、値型のデータに何が起こっているかを確認できます。ボックス化が実行されなかった場合、4 回呼び出すxため、プログラムは両方の呼び出しに対して 4 を書き出すと予想されます。ただし、実際に得られる結果は 1 と 2 です。理由は、ボクシングがコピーを作成したためです。GetI()F()

これは、値をボックス化する場合とボックス化しない場合に違いがあることを示しています

于 2011-04-28T00:35:27.490 に答える
2

問題は、「単なる」インターフェース型である値や変数などがないことです。代わりに、そのような変数に定義したり、そのような値にキャストしたりする試みが行われると、使用される実際の型は、事実上、「Objectインターフェイスを実装する型」になります。

この区別は、ジェネリクスで有効になります。ルーチンがタイプTwhereのパラメーターを受け入れるとしますT:IFoo。このようなルーチンに IFoo を実装する構造体を渡す場合、渡されたパラメーターは Object から継承するクラス型ではなく、適切な構造体型になります。ルーチンが渡されたパラメーターを type のローカル変数に割り当てる場合、Tパラメーターはボックス化せずに値によってコピーされます。IFooただし、type のローカル変数に割り当てられた場合、その変数の型は「Object実装する an IFoo」になるため、その時点でボクシングが必要になります。

でメソッドExecF<T>(ref T thing) where T:Iを呼び出すことができる静的メソッドを定義すると役立つ場合があります。このようなメソッドは、ボクシングを必要とせず、 によって実行される自己変異を尊重します。I.F()thingI.F()

于 2011-12-23T18:34:13.710 に答える