Вот рабочее решение
interface ISomeInterface {}
class X<I>
where I : ISomeInterface
{ }
class Y<T, I>
where T : X<I>
where I : ISomeInterface
{ }
class Z<I>
where I : ISomeInterface
{
Y<X<I>, I> MyOtherData { get; set; }
Y<X<ISomeInterface>, ISomeInterface> MyData { get; set; }
}
Обратите внимание на дополнительный универсальный параметр I
и ограничения, которые я добавил к определению типа Y
.
Ваше первоначальное определение Y
слишком ограничительно.C # делает различие между ISomeInterface
и любым типом, реализующим ISomeInterface
.
Почему это так?
.NET-компилятор имеет особый способ обработки универсальных типов.Для каждого универсального типа компилятор создаст отдельный тип на основе аргументов универсального типа, который вместо этого используется во время выполнения.Например, List<int>
и List<string>
будут совершенно разными типами, причем все будут демонстрировать поведение, определенное универсальным типом List<_>
, но с фактическим типом int
или string
, встроенным в конкретный тип, сгенерированный компилятором.
Когда вы определяете class Y<T> where T : X<ISomeInterface>
, вы «запечатываете» общий параметр X
, равный ISomeInterface
.Компилятор примет любой тип, наследующий X<ISomeInterface>
, но не X<SomethingElse>
, даже если SomethingElse
сам является реализацией ISomeInterface
.Это связано с тем, что фрагмент where T : X<ISomeInterface>
вызывает "запекание" ISomeInterface
в определении типа Y<T>
.Такое поведение является ожидаемым и является побочным эффектом того, как обобщенные элементы компилируются в реальный код.По той же причине нельзя делать следующее:
List<object> x = new List<string>();
, хотя тип string
наследуется от object
.