Мне пришлось долго и усердно думать о том, как это хорошо объяснить.Объяснить это, кажется, так же сложно, как понять.
Представьте, что у вас есть базовый класс Fruit.И у вас есть два подкласса Apple и Banana.
Fruit
/ \
Banana Apple
Вы создаете два объекта:
Apple a = new Apple();
Banana b = new Banana();
Для обоих этих объектов вы можете вписать их в объект Fruit.
Fruit f = (Fruit)a;
Fruit g = (Fruit)b;
Вы можете обрабатывать производныеклассы, как если бы они были их базовым классом.
Однако вы не можете обрабатывать базовый класс, как если бы он был производным классом
a = (Apple)f; //This is incorrect
Позволяет применить это к примеру List.
Предположим, вы создали два списка:
List<Fruit> fruitList = new List<Fruit>();
List<Banana> bananaList = new List<Banana>();
Вы можете сделать что-то вроде этого ...
fruitList.Add(new Apple());
и
fruitList.Add(new Banana());
, потому что это, по сути, их типизациякак вы добавляете их в список.Вы можете думать об этом так ...
fruitList.Add((Fruit)new Apple());
fruitList.Add((Fruit)new Banana());
Однако применение той же логики к обратному регистру вызывает некоторые красные флажки.
bananaList.Add(new Fruit());
- это то же самое, что
bannanaList.Add((Banana)new Fruit());
Поскольку вы не можете рассматривать базовый класс как производный класс, это приводит к ошибкам.
На всякий случай, если ваш вопрос вызвал ошибки, я тоже это объясню.
Вот класс Fruit
public class Fruit
{
public Fruit()
{
a = 0;
}
public int A { get { return a; } set { a = value } }
private int a;
}
и класс Banana
public class Banana: Fruit
{
public Banana(): Fruit() // This calls the Fruit constructor
{
// By calling ^^^ Fruit() the inherited variable a is also = 0;
b = 0;
}
public int B { get { return b; } set { b = value; } }
private int b;
}
Итак, представьте, что вы снова создали два объекта
Fruit f = new Fruit();
Banana ba = new Banana();
Помните, что у Banana есть две переменные "a" и "b", а у Fruit только одна переменная "a".Поэтому, когда вы делаете это ...
f = (Fruit)b;
f.A = 5;
Вы создаете полный объект Fruit.Но если бы вы сделали это ...
ba = (Banana)f;
ba.A = 5;
ba.B = 3; //Error!!!: Was "b" ever initialized? Does it exist?
Проблема в том, что вы не создаете полный класс Banana. Не все члены данных объявлены / инициализированы.
Сейчасчто я вернулся из душа и получил себе перекусить, вот где все становится немного сложнее.
Оглядываясь назад, я должен был отбросить метафору, когда разбирался со сложными вещами
давай сделаемдва новых класса:
public class Base
public class Derived : Base
Они могут делать все, что вам нравится
Теперь давайте определим две функции
public Base DoSomething(int variable)
{
return (Base)DoSomethingElse(variable);
}
public Derived DoSomethingElse(int variable)
{
// Do stuff
}
Это похоже на то, как «работает» васвсегда должен иметь возможность использовать производный класс, как если бы это был базовый класс, давайте применим его к интерфейсу
interface MyInterface<T>
{
T MyFunction(int variable);
}
Ключевое различие между out / in заключается в том, что Generic используется в качестве возвращаемого типа илипараметр метода, это первый случай.
позволяет определить класс, который реализует этот интерфейс:
public class Thing<T>: MyInterface<T> { }
, тогда мы создадим два объекта:
MyInterface<Base> base = new Thing<Base>;
MyInterface<Derived> derived = new Thing<Derived>;
ЕслиВы должны были сделать это:
base = derived;
Вы бы получилиt ошибка типа «не может неявно преобразовать из ...»
У вас есть два варианта: 1) явное преобразование их или 2) указание компилятору неявно преобразовывать их.
base = (MyInterface<Base>)derived; // #1
или
interface MyInterface<out T> // #2
{
T MyFunction(int variable);
}
Второй случай вступает в действие, если ваш интерфейс выглядит следующим образом:
interface MyInterface<T>
{
int MyFunction(T variable); // T is now a parameter
}
снова связывает его с двумя функциями
public int DoSomething(Base variable)
{
// Do stuff
}
public int DoSomethingElse(Derived variable)
{
return DoSomething((Base)variable);
}
, мы надеемся,Вы видите, как ситуация изменилась, но по сути это тот же тип преобразования.
Повторное использование тех же классов
public class Base
public class Derived : Base
public class Thing<T>: MyInterface<T> { }
и тех же объектов
MyInterface<Base> base = new Thing<Base>;
MyInterface<Derived> derived = new Thing<Derived>;
, есливы пытаетесь установить их равными
base = derived;
ваш компилятор снова будет кричать на вас, у вас есть те же опции, что и раньше
base = (MyInterface<Base>)derived;
или
interface MyInterface<in T> //changed
{
int MyFunction(T variable); // T is still a parameter
}
В основномиспользовать, когда универсальный будет использоваться только в качестве возвращаемого типа методов интерфейса.Используйте, когда он будет использоваться как параметр метода.Те же правила применяются и при использовании делегатов.
Есть странные исключения, но я не буду беспокоиться о них здесь.
Извините за любые неосторожные ошибки заранее =)