c# - method - todavía confundido acerca de la covarianza y la contravarianza y entrada/salida
how to use func c# (5)
ok, leí un poco sobre este tema en stackoverflow, vi this y this , pero todavía estoy un poco confundido sobre co / contra-varianza.
de here
La covarianza permite que un tipo "más grande" (menos específico) sea sustituido en una API donde el tipo original solo se usa en una posición de "salida" (por ejemplo, como un valor de retorno). La contradicción permite que un tipo "más pequeño" (más específico) sea sustituido en una API donde el tipo original solo se usa en una posición de "entrada".
Sé que tiene que ver con el tipo de seguridad.
sobre la cosa de in/out
. ¿Puedo decir que uso cuando tengo que escribir en él, y cuando está solo de lectura? y in
contra-varianza de los medios, out
co-varianza. pero de la explicación anterior ...
y here
Por ejemplo, una
List<Banana>
no puede tratarse como unaList<Fruit>
porque lalist.Add(new Apple())
es válida para la Lista pero no para laList<Banana>
.
así que no debería ser, si fuera a usar in
/ voy a escribir en el objeto, debe ser más grande más genérico.
Sé que esta pregunta ha sido formulada pero todavía muy confusa.
Antes de llegar al tema, hagamos un repaso rápido:
La referencia de clase base puede contener un objeto de clase derivado PERO no viceversa.
Covarianza : la covarianza le permite pasar un objeto de tipo derivado donde se espera un objeto de tipo base La covarianza se puede aplicar a un delegado, genérico, matriz, interfaz, etc.
Contravarianza: la contradicción se aplica a los parámetros. Permite que un método con el parámetro de una clase base se asigne a un delegado que espera el parámetro de una clase derivada
Eche un vistazo al ejemplo simple a continuación:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace CovarianceContravarianceDemo
{
//base class
class A
{
}
//derived class
class B : A
{
}
class Program
{
static A Method1(A a)
{
Console.WriteLine("Method1");
return new A();
}
static A Method2(B b)
{
Console.WriteLine("Method2");
return new A();
}
static B Method3(B b)
{
Console.WriteLine("Method3");
return new B();
}
public delegate A MyDelegate(B b);
static void Main(string[] args)
{
MyDelegate myDel = null;
myDel = Method2;// normal assignment as per parameter and return type
//Covariance, delegate expects a return type of base class
//but we can still assign Method3 that returns derived type and
//Thus, covariance allows you to assign a method to the delegate that has a less derived return type.
myDel = Method3;
A a = myDel(new B());//this will return a more derived type object which can be assigned to base class reference
//Contravariane is applied to parameters.
//Contravariance allows a method with the parameter of a base class to be assigned to a delegate that expects the parameter of a derived class.
myDel = Method1;
myDel(new B()); //Contravariance,
}
}
}
En palabras de Jons:
La covarianza permite que un tipo "más grande" (menos específico) sea sustituido en una API donde el tipo original solo se usa en una posición de "salida" (por ejemplo, como un valor de retorno). La contradicción permite que un tipo "más pequeño" (más específico) sea sustituido en una API donde el tipo original solo se usa en una posición de "entrada".
Encontré su explicación confusa al principio, pero tenía sentido para mí que una vez que se sustituye se enfatiza, combinada con el ejemplo de la guía de programación de C #:
// Covariance.
IEnumerable<string> strings = new List<string>();
// An object that is instantiated with a more derived type argument
// is assigned to an object instantiated with a less derived type argument.
// Assignment compatibility is preserved.
IEnumerable<object> objects = strings;
// Contravariance.
// Assume that the following method is in the class:
// static void SetObject(object o) { }
Action<object> actObject = SetObject;
// An object that is instantiated with a less derived type argument
// is assigned to an object instantiated with a more derived type argument.
// Assignment compatibility is reversed.
Action<string> actString = actObject;
El conversor delegado me ayuda a entenderlo:
delegate TOutput Converter<in TInput, out TOutput>(TInput input);
TOutput
representa covarianza cuando un método devuelve un tipo más específico .
TInput
representa la contravariancia cuando un método pasa un tipo menos específico .
public class Dog { public string Name { get; set; } }
public class Poodle : Dog { public void DoBackflip(){ System.Console.WriteLine("2nd smartest breed - woof!"); } }
public static Poodle ConvertDogToPoodle(Dog dog)
{
return new Poodle() { Name = dog.Name };
}
List<Dog> dogs = new List<Dog>() { new Dog { Name = "Truffles" }, new Dog { Name = "Fuzzball" } };
List<Poodle> poodles = dogs.ConvertAll(new Converter<Dog, Poodle>(ConvertDogToPoodle));
poodles[0].DoBackflip();
La covarianza es bastante fácil de entender. Es natural. La contradicción es más confusa.
Mire de cerca este ejemplo de MSDN . Vea cómo SortedList espera un IComparer, pero están pasando en ShapeAreaComparer: IComparer. La Forma es del tipo "más grande" (está en la firma del destinatario, no de la persona que llama), pero la contravarianza permite que el tipo "más pequeño" - el Círculo - sea sustituido por todas partes en el Componente de Forma que normalmente tomaría una Forma.
Espero que ayude.
Tanto la covarianza como la contravariancia en C # 4.0 se refieren a la capacidad de usar una clase derivada en lugar de la clase base. Las palabras clave de entrada / salida son sugerencias del compilador para indicar si los parámetros de tipo se usarán o no para entrada y salida.
Covarianza
La covarianza en C # 4.0 es ayudada por out
palabra clave out
y significa que un tipo genérico que usa una clase derivada del parámetro out
type está OK. Por lo tanto
IEnumerable<Fruit> fruit = new List<Apple>();
Como Apple
es una Fruit
, la List<Apple>
puede usar de forma segura como IEnumerable<Fruit>
Contravariancia
La contradicción es la palabra clave in
y denota los tipos de entrada, generalmente en delegados. El principio es el mismo, significa que el delegado puede aceptar más clase derivada.
public delegate void Func<in T>(T param);
Esto significa que si tenemos un Func<Fruit>
, se puede convertir a Func<Apple>
.
Func<Fruit> fruitFunc = (fruit)=>{};
Func<Apple> appleFunc = fruitFunc;
¿Por qué se les llama co / contravariación si son básicamente la misma cosa?
Porque a pesar de que el principio es el mismo, fundición segura desde derivada a base, cuando se usa en los tipos de entrada, podemos emitir un tipo menos derivado ( Func<Fruit>
) a un tipo más derivado ( Func<Apple>
), que Tiene sentido, ya que cualquier función que tome Fruit
, también puede tomar Apple
.
Tuve que pensar mucho sobre cómo explicar esto bien. Explicar parece ser tan difícil como entenderlo.
Imagina que tienes una fruta de la clase base. Y tienes dos subclases de Apple y Banana.
Fruit
/ /
Banana Apple
Usted crea dos objetos:
Apple a = new Apple();
Banana b = new Banana();
Para ambos objetos, puede encasillarlos en el objeto Fruta.
Fruit f = (Fruit)a;
Fruit g = (Fruit)b;
Puede tratar las clases derivadas como si fueran su clase base.
Sin embargo, no se puede tratar una clase base como si fuera una clase derivada
a = (Apple)f; //This is incorrect
Vamos a aplicar esto al ejemplo de la lista.
Supongamos que creó dos listas:
List<Fruit> fruitList = new List<Fruit>();
List<Banana> bananaList = new List<Banana>();
Puedes hacer algo como esto ...
fruitList.Add(new Apple());
y
fruitList.Add(new Banana());
porque, en esencia, los está encasillando a medida que los agrega a la lista. Puedes pensarlo así ...
fruitList.Add((Fruit)new Apple());
fruitList.Add((Fruit)new Banana());
Sin embargo, aplicar la misma lógica al caso inverso genera algunas banderas rojas.
bananaList.Add(new Fruit());
es lo mismo que
bannanaList.Add((Banana)new Fruit());
Como no se puede tratar una clase base como una clase derivada, esto produce errores.
En caso de que tu pregunta sea por qué esto causa errores, también lo explicaré.
Aquí está la clase de fruta
public class Fruit
{
public Fruit()
{
a = 0;
}
public int A { get { return a; } set { a = value } }
private int a;
}
y aquí está la clase 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;
}
Entonces imagina que nuevamente has creado dos objetos
Fruit f = new Fruit();
Banana ba = new Banana();
recuerde que Banana tiene dos variables "a" y "b", mientras que Fruit solo tiene una, "a". Entonces cuando haces esto ...
f = (Fruit)b;
f.A = 5;
Usted crea un objeto Fruta completo. Pero si fueras a hacer esto ...
ba = (Banana)f;
ba.A = 5;
ba.B = 3; //Error!!!: Was "b" ever initialized? Does it exist?
El problema es que no se crea una clase completa de Banana. No todos los miembros de datos se declaran / inicializan.
Ahora que he vuelto de la ducha y me he comido un bocado aquí, se vuelve un poco complicado.
En retrospectiva, debería haber abandonado la metáfora cuando me metí en las cosas complicadas
vamos a hacer dos nuevas clases:
public class Base
public class Derived : Base
Ellos pueden hacer lo que quieras
Ahora vamos a definir dos funciones
public Base DoSomething(int variable)
{
return (Base)DoSomethingElse(variable);
}
public Derived DoSomethingElse(int variable)
{
// Do stuff
}
Esto es similar a cómo funciona "fuera", siempre debería ser capaz de utilizar una clase derivada como si fuera una clase base, vamos a aplicar esto a una interfaz
interface MyInterface<T>
{
T MyFunction(int variable);
}
La diferencia clave entre out / in es cuando el genérico se utiliza como un tipo de devolución o un parámetro de método, este es el caso anterior.
vamos a definir una clase que implemente esta interfaz:
public class Thing<T>: MyInterface<T> { }
luego creamos dos objetos:
MyInterface<Base> base = new Thing<Base>;
MyInterface<Derived> derived = new Thing<Derived>;
Si fueras a hacer esto:
base = derived;
Obtendría un error como "no se puede convertir implícitamente de ..."
Tiene dos opciones, 1) convertirlas explícitamente o, 2) decirle al compilador que las convierta implícitamente.
base = (MyInterface<Base>)derived; // #1
o
interface MyInterface<out T> // #2
{
T MyFunction(int variable);
}
El segundo caso entra para jugar si su interfaz se ve así:
interface MyInterface<T>
{
int MyFunction(T variable); // T is now a parameter
}
relacionándolo con las dos funciones nuevamente
public int DoSomething(Base variable)
{
// Do stuff
}
public int DoSomethingElse(Derived variable)
{
return DoSomething((Base)variable);
}
con suerte, verá cómo se ha invertido la situación, pero es esencialmente el mismo tipo de conversión.
Usando las mismas clases otra vez
public class Base
public class Derived : Base
public class Thing<T>: MyInterface<T> { }
y los mismos objetos
MyInterface<Base> base = new Thing<Base>;
MyInterface<Derived> derived = new Thing<Derived>;
si intentas establecerlos iguales
base = derived;
su compilador le gritará nuevamente, usted tiene las mismas opciones que antes
base = (MyInterface<Base>)derived;
o
interface MyInterface<in T> //changed
{
int MyFunction(T variable); // T is still a parameter
}
Básicamente utilizar cuando el genérico solo se utilizará como un tipo de retorno de los métodos de interfaz. Use en cuando se va a usar como un parámetro de Método. Las mismas reglas se aplican al usar delegados también.
Hay extrañas excepciones, pero no voy a preocuparme por ellas aquí.
Perdón por cualquier error descuidado de antemano =)