0

チャットで誰かと関連事項について話し合っていたところ、予想とは異なる動作をするこのコードを思いつきました。

class Program
{
    static void Main(string[] args)
    {
        new Test<SomeObject>();
        Console.ReadKey();
    }
}

class SomeObject
{
    public SomeObject() { }

    public new string ToString()
    {
        return "Hello world.";
    }
}

class Test<T> where T : new()
{
    public Test()
    {
        T t = new T();
        object t1 = t;
        Console.WriteLine(t.ToString());
        Console.WriteLine(t1.ToString());
    }
}

出力は次のとおりです。

<ProjectName>.SomeObject
<ProjectName>.SomeObject

最初の行はジェネリック型から書かれているので、SomeObjectで定義されたToString()メソッドを使用することを期待していました。これは、実行時に型がどうなるかということです。

4

3 に答える 3

2

Ben Voigt が彼のコメントで答えをくれたと思います。

new非表示 ( ) メソッドの実装をジェネリック制約として宣言する型を指定することで、期待する結果を得ることができます。

class Test<T> where T : SomeObject, new()
{
    public Test()
    {
        T t = new T();
        object t1 = t;
        Console.WriteLine(t.ToString());
        Console.WriteLine(t1.ToString());
    }
}

これは以下を出力します:

Hello world.
Program.SomeObject

編集: コンパイラは、ジェネリック型のメンバー呼び出しをジェネリック制約に対して解決します。これは、MSDN C# Programming Guide on Constraints on Type Parametersで暗示されています。

型パラメーターを制約することにより、許容される操作とメソッド呼び出しの数を、制約する型とその継承階層内のすべての型でサポートされているものに増やします。したがって、ジェネリック クラスまたはジェネリック メソッドを設計するときに、ジェネリック メンバーに対して単純な代入以外の操作を実行したり、System.Object でサポートされていないメソッドを呼び出したりする場合は、型パラメーターに制約を適用する必要があります。

Foo問題を明確にするために:クラスで新しいメソッド を定義したとします。

class SomeObject
{
    public SomeObject() { }

    public void Foo() { }
}

呼び出そFooうとすると、コンパイル時エラーが発生します。コンパイラがジェネリック型について知っている唯一のことTは、それがパラメーターなしのコンストラクターを持っているということです。それが定義する可能性のあるメソッドについての知識はありません。

class Test<T> where T : new()
{
    public Test()
    {
        T t = new T();
        t.Foo();   // Error: 'T' does not contain a definition for 'Foo' 
                   //        and no extension method 'Foo' accepting a
                   //        first argument of type 'T' could be found
    }
}

Tただし、 typeであることを制約すると、コンパイラはクラス内でSomeObjectの定義を探すことを認識します。FooSomeObject

class Test<T> where T : SomeObject, new()
{
    public Test()
    {
        T t = new T();
        t.Foo();   // SomeObject.Foo gets called
    }
}

非表示メンバーの場合も同様です。

于 2012-05-05T20:30:59.203 に答える
1

では、 に制約がないため、コンパイラはが実際に になることをTest<T>知りません。したがって、それが であると想定することしかできず、 への呼び出しは仮想メソッドを呼び出すことになり、そうではありませんTSomeObjectTtobjectt.ToString()Object.ToString SomeObject.ToString()

于 2012-05-05T20:39:02.787 に答える
0

これはジェネリックとはまったく関係がなく、既存のメソッドをオーバーライドするのではなく、という新しいメソッドを宣言したという事実にすべて関係しています。ToString

代わりにメソッドをオーバーライドした場合は、それが使用されていたはずです。これは、レイト バインドと比較して、アーリー バインドとは何の関係もありません (ここの他の回答で示されているように)。

ToString参照での呼び出しがT参照と変わらない理由は、ここで使用できるすべての がその新しいメソッドを定義したobjectことをコンパイラが確認する方法がないため、 object から継承されたものにフォールバックする必要があるためです。すべての場合において。 TToString

コンパイラは のすべてのバリエーションで使用されるメソッドを生成することに注意してくださいT.私はそれが違いを生むとは思わない)そのため、コンパイラはToStringこの場合オーバーライドされたことを知る方法がありません.

一方、問題の が の子孫であると述べた場合、コンパイラは使用する新しいメソッドがあることを認識しますが、この知識は だけではコンパイラに利用できません。TSomeObject ToStringT

于 2012-05-05T20:33:58.410 に答える