=============
ACTUALIZACIÓN: Utilicé esta respuesta como base para esta entrada de blog:
¿Por qué los parámetros ref y out no permiten la variación de tipo?
Consulte la página del blog para obtener más comentarios sobre este tema. Gracias por la gran pregunta.
=============
Supongamos que usted tiene clases Animal
, Mammal
, Reptile
, Giraffe
, Turtle
y Tiger
, con las relaciones de subclases obvias.
Ahora suponga que tiene un método void M(ref Mammal m)
. M
Puede leer y escribir tanto m
.
¿Se puede pasar una variable de tipo Animal
a M
?
No. Esa variable podría contener a Turtle
, pero M
supondrá que solo contiene mamíferos. A Turtle
no es a Mammal
.
Conclusión 1 : los ref
parámetros no pueden hacerse "más grandes". (Hay más animales que mamíferos, por lo que la variable se está haciendo "más grande" porque puede contener más cosas).
¿Se puede pasar una variable de tipo Giraffe
a M
?
No. M
puede escribir m
y M
puede querer escribir un correo Tiger
electrónico m
. Ahora ha puesto una Tiger
variable en una variable que en realidad es de tipo Giraffe
.
Conclusión 2 : los ref
parámetros no pueden hacerse "más pequeños".
Ahora considera N(out Mammal n)
.
¿Se puede pasar una variable de tipo Giraffe
a N
?
No. N
puede escribir n
y N
puede querer escribir a Tiger
.
Conclusión 3 : los out
parámetros no pueden hacerse "más pequeños".
¿Se puede pasar una variable de tipo Animal
a N
?
Hmm
¿Bueno, por qué no? N
no puede leer n
, solo puede escribirle, ¿verdad? Escribes un Tiger
a una variable de tipo Animal
y estás listo, ¿verdad?
Incorrecto. La regla no es " N
solo se puede escribir n
".
Las reglas son, brevemente:
1) N
tiene que escribir n
antes de que N
vuelva normalmente. (Si N
lanza, todas las apuestas están canceladas).
2) N
tiene que escribir algo n
antes de que lea algo n
.
Eso permite esta secuencia de eventos:
- Declarar un campo
x
de tipo Animal
.
- Pase
x
como out
parámetro a N
.
N
escribe un Tiger
en n
, que es un alias para x
.
- En otro hilo, alguien escribe un
Turtle
en x
.
N
intenta leer el contenido de n
, y descubre un Turtle
en lo que piensa que es una variable de tipo Mammal
.
Claramente queremos que sea ilegal.
Conclusión 4 : los out
parámetros no pueden hacerse "más grandes".
Conclusión final : Ni ref
tampoco out
parámetros pueden variar sus tipos. Hacer lo contrario es romper la seguridad de tipo verificable.
Si estas cuestiones en la teoría básica de tipos le interesan, considere leer mi serie sobre cómo funcionan la covarianza y la contravarianza en C # 4.0 .
out
parámetros no pueden hacerse "más grandes"? La secuencia que ha descrito se puede aplicar a cualquier variable, no solo a laout
variable de parámetro. Y también las necesidades de los lectores para emitir el valor del argumento aMammal
antes de intentar acceder a él comoMammal
y por supuesto que puede fallar si no es consideradoPorque en ambos casos debe poder asignar un valor al parámetro ref / out.
Si intenta pasar b al método Foo2 como referencia, y en Foo2 intenta asignar a = new A (), esto sería inválido.
La misma razón por la que no puedes escribir:
fuente
Estás luchando con el clásico problema OOP de covarianza (y contravarianza), mira wikipedia : aunque este hecho puede desafiar las expectativas intuitivas, es matemáticamente imposible permitir la sustitución de clases derivadas en lugar de las bases por argumentos mutables (asignables) (y también contenedores cuyos artículos son asignables, por la misma razón) sin dejar de respetar el principio de Liskov . Por qué eso es así se esboza en las respuestas existentes y se explora más profundamente en estos artículos wiki y enlaces a partir de ellas.
Los lenguajes OOP que parecen hacerlo mientras permanecen tradicionalmente estáticamente seguros de tipos son "trampas" (insertando verificaciones de tipos dinámicos ocultos o requiriendo un examen en tiempo de compilación de TODAS las fuentes para verificar); la elección fundamental es: renunciar a esta covarianza y aceptar la perplejidad de los profesionales (como lo hace C # aquí), o pasar a un enfoque de tipeo dinámico (como lo hizo el primer lenguaje OOP, Smalltalk), o pasar a inmutable (single- asignación) datos, como lo hacen los lenguajes funcionales (bajo inmutabilidad, puede admitir covarianza y también evitar otros acertijos relacionados, como el hecho de que no puede tener Rectángulo cuadrado de subclase en un mundo de datos mutables).
fuente
Considerar:
Violaría la seguridad de tipos
fuente
var
estuvo totalmente mal. Fijo.Si bien las otras respuestas han explicado sucintamente el razonamiento detrás de este comportamiento, creo que vale la pena mencionar que si realmente necesita hacer algo de esta naturaleza, puede lograr una funcionalidad similar al convertir Foo2 en un método genérico, como tal:
fuente
Porque dar
Foo2
unref B
resultado daría como resultado un objeto mal formado porqueFoo2
solo sabe cómo llenarA
parte de élB
.fuente
¿No es ese el compilador que le dice que le gustaría que expulse el objeto explícitamente para que pueda estar seguro de cuáles son sus intenciones?
fuente
Tiene sentido desde una perspectiva de seguridad, pero hubiera preferido que el compilador diera una advertencia en lugar de un error, ya que hay usos legítimos de objetos polimorfos pasados por referencia. p.ej
Esto no se compilará, pero ¿funcionaría?
fuente
Si usa ejemplos prácticos para sus tipos, lo verá:
Y ahora tienes tu función que toma el ancestro ( es decir
Object
):¿Qué puede estar mal con eso?
Acabas de asignar una
Bitmap
a tuSqlConnection
.Eso no es bueno.
Intente nuevamente con otros:
Rellenaste
OracleConnection
demasiadoSqlConnection
.fuente
En mi caso, mi función aceptó un objeto y no pude enviar nada, así que simplemente hice
Y eso funciona
My Foo está en VB.NET y comprueba el tipo dentro y hace mucha lógica
Pido disculpas si mi respuesta es duplicada pero otras fueron demasiado largas
fuente