2012-05-05 3 views
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 

Поскольку первая строка написана от родового типа, как я ожидал, что использовать метод ToString(), определенный в SomeObject, так это то, что тип стал бы в перспективе не так ли?

+0

Нет, дженерики рано переплете. Разрешение перегрузки использует общие ограничения, но не фактический тип. –

ответ

2

Я считаю, что Бен Вейгт дал вам ответ в своем комментарии.

Вы могли бы достичь результата вы ожидаете, указав тип, который декларирует скрытие (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 на 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 быть типа SomeObject, то компилятор будет знать, искать определения Foo в SomeObject класса:

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

Аргументация очень похож на скрытый члены.

+0

О, ничего себе. Мне это кажется даже незнакомым! Вы знаете причины этого? –

+0

@SpencerRuport, когда вы указываете ограничение наследования для T для SomeObject, вы даете больше информации компилятору. Теперь он знает, что T всегда является SomeObject или производным классом, поэтому он знает, что нужно вызвать новый метод ToString –

+0

@SpencerRuport: Отвечено в ответ. – Douglas

0

Это не имеет абсолютно ничего общего с генерик, и все, что связанно с тем, что вы объявленным методом нового под названием ToString, вместо переопределяет существующие.

Если вы переопределили метод вместо этого, он был бы использован, это не имеет ничего общего с ранней привязкой по сравнению с поздними ограничениями (как указано в других ответах здесь.)

Причина вызова ToString на T ссылки не отличается от object ссылки является то, что компилятор не имеет возможности проверить, что всеT «s, которые могут быть использованы здесь определил что новый метод ToString , и поэтому он должен вернуться к тому, который унаследован от объекта в всех случаях.

Обратите внимание, что компилятор будет производить метод, который будет использоваться всех вариациями T, независимо, поэтому он должен использовать знание он имеет около T, и вы еще не сказали, что вы унаследовали от интерфейса или класс (хотя в этом случае я сомневаюсь, что это будет иметь значение), поэтому компилятор не знает, что ToString был переопределен в этом случае.

Если, с другой стороны, вы было заявил, что T в вопросе является потомком SomeObject, компилятор будет знать, что он имеет новыйToString метод использовать, но это знание не доступно компилятор только с T.

+1

Это было его намерение скрыть унаследованный метод - иначе он не ожидал увидеть какую-либо разницу при вызове его из ссылки на объект. – Douglas

1

В Test<T>, компилятор не знает, что T на самом деле будет SomeObject, так как нет никаких ограничений на T. Таким образом, он может только предполагает, что t является object, и призыв к t.ToString() результатов в вызове виртуальной Object.ToString метод, неSomeObject.ToString()

Смежные вопросы