Это получилось немного длинным, так что вот быстрая версия:
Почему это вызывает исключение 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<T>()
, потому что все происходит от Object
.
Тем не менее, я все еще ожидал получить ошибку компилятора. В смысле языка С# это нарушает правило, что "ограничения на C.Foo() должны соответствовать ограничениям на I.Foo()", и я думал, что компилятор был бы приверженцем правил. Но он компилируется. Кажется, компилятор видит, что я делаю, понимает, что он в безопасности, и закрывает глаза.
Я думал, что с этим справился, но время исполнения говорит не так быстро. Если я попытаюсь создать экземпляр D
, я получаю исключение TypeLoadException: "Метод" C`1.Foo "в типе" D "попытался неявно реализовать метод интерфейса с более слабыми ограничениями параметров типа".
Но разве эта ошибка технически неверна? Не использует Object
для C<T1>
отрицать ограничение на C.Foo()
, тем самым делая его эквивалентным - NOT сильнее, чем - 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<T>()
.
Почему? Есть ли ошибка во время выполнения, или (что более вероятно), есть причина для этого исключения, которое я не вижу - в этом случае компилятор не остановил меня?
Интересно, что если сценарий отменяется путем перемещения ограничения от класса к интерфейсу...
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, и это не будет компилироваться. Я чувствую себя немного грязно, делая это, потому что я не уверен, намеренно ли он работает таким образом.