Почему этот общий сценарий вызывает исключение TypeLoadException?

Это немного затянуло, поэтому вот краткая версия:

Почему это вызывает исключение TypeLoadException во время выполнения? (И должен ли компилятор мешать мне это сделать?)

interface I
{
    void Foo<T>();
}

class C<T1>
{
    public void Foo<T2>() where T2 : T1 { }
}

class D : C<System.Object>, I { } 

исключение возникает, если вы пытаетесь создать экземпляр D.


Более длинная, более исследовательская версия:

Учтите:

interface I
{
    void Foo<T>();
}

class C<T1>
{
    public void Foo<T2>() where T2 : T1 { }
}

class some_other_class { }

class D : C<some_other_class>, I { } // compiler error CS0425

Это недопустимо, поскольку ограничения типа для C.Foo () не соответствуют этим на I.Foo () . Он генерирует ошибку компилятора CS0425.

Но я подумал, что смогу нарушить правило:

class D : C<System.Object>, I { } // yep, it compiles

Используя Object в качестве ограничения для T2, я отрицаю , что ограничение. Я могу безопасно передать любой тип в D.Foo () , потому что все происходит от Object .

Тем не менее, я все еще ожидал получить ошибку компилятора. В смысле языка C # он нарушает правило, согласно которому «ограничения на C.Foo () должны соответствовать ограничениям на I.Foo ()», и я думал, что компилятор будет придерживаться правил . Но он компилируется. Кажется, что компилятор видит, что я делаю, понимает, что это безопасно, и закрывает глаза.

Я думал, что это сошло мне с рук, но среда выполнения говорит не так быстро . Если я пытаюсь создать экземпляр D , я получаю исключение TypeLoadException: «Метод 'C`1.Foo' для типа 'D' пытался неявно реализовать метод интерфейса с более слабыми ограничениями параметров типа».

Но разве эта ошибка технически не ошибочна? Не использует Объект для C , отменяет ограничение на C.Foo () , тем самым делая его эквивалентным - НЕ сильнее, чем - I.Foo () ? Компилятор, кажется, соглашается, но среда выполнения - нет.

Чтобы доказать свою точку зрения, я упростил ее, убрав D из уравнения:

interface I<T1>
{
    void Foo<T2>() where T2 : T1;
}

class some_other_class { }

class C : I<some_other_class> // compiler error CS0425
{
    public void Foo<T>() { }
}

Но:

class C : I<Object> // compiles
{
    public void Foo<T>() { }
}

Это компилируется и запускается идеально для любого типа, переданного в Foo () .

Почему? Есть ли ошибка во время выполнения или (что более вероятно) есть причина для этого исключения, которую я не вижу - и в этом случае компилятор не должен был останавливать меня?

Интересно, если сценарий отменяется перемещение ограничения из класса в интерфейс ...

interface I<T1>
{
    void Foo<T2>() where T2 : T1;
}

class C
{
    public void Foo<T>() { }
}

class some_other_class { }

class D : C, I<some_other_class> { } // compiler error CS0425, as expected

И снова я отменяю ограничение:

class D : C, I<System.Object> { } // compiles

На этот раз все работает нормально!

D d := new D();
d.Foo<Int32>();
d.Foo<String>();
d.Foo<Enum>();
d.Foo<IAppDomainSetup>();
d.Foo<InvalidCastException>();

Все идет, и это имеет для меня смысл. (То же, с D в уравнении или без него)

Так почему же первый способ не работает?

Приложение:

Я забыл добавить, что существует простой способ обхода исключения TypeLoadException:

interface I
{
    void Foo<T>();
}

class C<T1>
{
    public void Foo<T2>() where T2 : T1 { }
}

class D : C<Object>, I 
{
    void I.Foo<T>() 
    {
        Foo<T>();
    }
}

Явная реализация I.Foo () в порядке. Только неявная реализация вызывает исключение TypeLoadException. Теперь я могу это сделать:

        I d = new D();
        d.Foo<any_type_i_like>();

Но это все еще особый случай. Попробуйте использовать что-нибудь еще, кроме System.Object, и это не будет компилироваться. Я чувствую себя немного грязным, делая это, потому что не уверен, что это работает намеренно.

18
задан Igby Largeman 20 May 2011 в 16:50
поделиться