Мне пришлось долго и усердно думать, как это хорошо объяснить. Объяснить это так же сложно, как и понять.
Представьте, что у вас есть 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<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;
Вы получите сообщение об ошибке «не может неявно преобразовать из ...»
У вас есть два варианта: 1) явно преобразовать их или 2) указать компилятору неявно преобразовать их.
base = (MyInterface<Base>)derived; // #1
or
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;
or
interface MyInterface<in T> //changed
{
int MyFunction(T variable); // T is still a parameter
}
В основном используйте out, когда общий тип будет использоваться только как возвращаемый тип методов интерфейса. Используйте, когда он будет использоваться в качестве параметра метода. Те же правила применяются и при использовании делегатов.
Бывают странные исключения, но я не буду о них беспокоиться.
Заранее извиняюсь за неосторожные ошибки =)
person
Community
schedule
10.08.2010