Это немного затянуло, поэтому вот краткая версия:
Почему это вызывает исключение 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, и это не будет компилироваться. Я чувствую себя немного грязным, делая это, потому что не уверен, что это работает намеренно.