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