2009-07-30 14 views
112

tome las siguientes:¿Por qué 'ref' y 'out' no admiten el polimorfismo?

class A {} 

class B : A {} 

class C 
{ 
    C() 
    { 
     var b = new B(); 
     Foo(b); 
     Foo2(ref b); // <= compile-time error: 
        // "The 'ref' argument doesn't match the parameter type" 
    } 

    void Foo(A a) {} 

    void Foo2(ref A a) {} 
} 

¿Por qué se produce el error de tiempo de compilación anterior? Esto ocurre con los argumentos ref y out.

Respuesta

155

=============

ACTUALIZACIÓN: He utilizado esta respuesta como la base para esta entrada de blog:

Why do ref and out parameters not allow type variation?

Ver la página del blog para obtener más comentario sobre este tema. Gracias por la gran pregunta.

=============

Supongamos que tiene clases Animal, Mammal, Reptile, Giraffe, Turtle y Tiger, con las relaciones de subclases obvias.

Supongamos que tiene un método void M(ref Mammal m). M puede leer y escribir m.


Se puede pasar una variable de tipo Animal-M?

No. Esa variable podría contener un Turtle, pero M asumirá que contenga sólo mamíferos. Un Turtle no es un Mammal.

Conclusión 1: ref los parámetros no se pueden hacer "más grandes". (Hay más animales que los mamíferos, por lo que la variable es cada vez "más grande", ya que puede contener más cosas.)


Se puede pasar una variable de tipo Giraffe a M?

M puede escribir en m, y M puede ser que desee escribir una Tiger en m. Ahora ha puesto un Tiger en una variable que en realidad es del tipo Giraffe.

Conclusión 2: ref los parámetros no se pueden hacer "más pequeños".


Considera ahora N(out Mammal n).

¿Se puede pasar una variable del tipo Giraffe al N?

N puede escribir en n y N puede ser que desee escribir una Tiger.

Conclusión 3: out los parámetros no pueden hacerse "más pequeños".


Se puede pasar una variable de tipo Animal-N?

Hmm.

Bueno, ¿por qué no? N no se puede leer desde n, solo puede escribir en él, ¿no? Usted escribe un Tiger en una variable del tipo Animal y ya está todo listo, ¿verdad?

Wrong. La regla no es "N solo puede escribir en n".

Las reglas son, en pocas palabras:

1) N tiene que escribir en n antes N vuelve normalmente. (Si N tiros, todas las apuestas están apagadas.)

2) N tiene que escribir algo a n antes de que se lee algo de n.

que permite esta secuencia de eventos:

  • declarar un campo de tipo xAnimal.
  • Pase x como un parámetro out a N.
  • N escribe Tiger en n, que es un alias de x.
  • En otro hilo, alguien escribe Turtle en x.
  • N intenta leer el contenido de n, y descubre un Turtle en lo que cree que es una variable del tipo Mammal.

Es evidente que queremos que sea ilegal.

Conclusión 4: out los parámetros no se pueden hacer "más grandes".


Conclusión final: Ni ref ni out parámetros pueden variar sus tipos. Hacer lo contrario es romper la seguridad del tipo verificable.

Si estos problemas en la teoría de tipos básicos le interesan, considere leer my series on how covariance and contravariance work in C# 4.0.

+6

+1. Gran explicación usando ejemplos de clase del mundo real que demuestran claramente los problemas (es decir, explicar con A, B y C hace que sea más difícil demostrar por qué no funciona). –

+4

Me siento honrado de leer este proceso de pensamiento. ¡Creo que será mejor que regrese a los libros! –

+0

En este caso, realmente no podemos usar la variable de clase abstracta como argumentos y transmitir su objeto de clase derivado. –

27

Porque en ambos casos debe poder asignar valor al parámetro ref/out.

Si intenta pasar b al método Foo2 como referencia, y en Foo2 intenta asignar un = new A(), esto no sería válido.
la misma razón que no se puede escribir:

B b = new A(); 
+1

Ninj'd por 4 segundos! :) – CannibalSmith

+0

+1 Directo al grano y explica perfectamente la razón. –

2

porque dar un Foo2ref B se traduciría en un objeto con formato incorrecto porque Foo2 sólo sabe cómo llenar A parte de B.

4

considerar:

class C : A {} 
class B : A {} 

void Foo2(ref A a) { a = new C(); } 

B b = null; 
Foo2(ref b); 

Violaría seguridad de tipos

+0

Es más el tipo inferido poco claro de "b" debido a la var que es el problema allí. –

+0

Supongo que en la línea 6 quería decir => B b = null; –

+0

@amiralles - sí, esa 'var' era totalmente incorrecta. Fijo. –

9

Usted está luchando con el problema clásico de la programación orientada a objetos covarianza (y contravarianza), ver wikipedia: tanto como este hecho puede desafiar Expectativas intuitivas, es matemáticamente imposible permitir la sustitución de clases derivadas en lugar de las bases para argumentos mutables (asignables) (y también contenedores cuyos elementos son asignables, por el mismo motivo) mientras se sigue respetando Liskov's principle. Por qué es así se esboza en las respuestas existentes, y se explora más profundamente en estos artículos wiki y enlaces desde allí.

Los lenguajes OOP que parecen hacerlo mientras permanecen tradicionalmente estáticos son "trampas" (insertando comprobaciones dinámicas ocultas o requiriendo el 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 practicantes (como C# lo hace aquí), o pasar a un enfoque de tipado dinámico (como lo hizo el primer lenguaje OOP, Smalltalk), o cambiar a inmutable (single- asignación), como en los lenguajes funcionales (bajo inmutabilidad, puede admitir la covarianza y también evitar otros acertijos relacionados, como el hecho de que no puede haber un rectángulo de subclase cuadrado en un mundo de datos mutables).

0

¿No es que el compilador le dice que le gustaría que expulse explícitamente el objeto para que pueda estar seguro de saber cuáles son sus intenciones?

Foo2(ref (A)b) 
+0

No se puede hacer eso, "Un argumento ref o out debe ser una variable asignable" –

0

tiene sentido desde una perspectiva de seguridad, pero yo hubiera preferido que el compilador dio una advertencia en lugar de un error, ya que hay usos legítimos de los objetos polymoprhic pasados ​​por referencia. p.ej.

class Derp : interfaceX 
{ 
    int somevalue=0; //specified that this class contains somevalue by interfaceX 
    public Derp(int val) 
    { 
    somevalue = val; 
    } 

} 


void Foo(ref object obj){ 
    int result = (interfaceX)obj.somevalue; 
    //do stuff to result variable... in my case data access 
    obj = Activator.CreateInstance(obj.GetType(), result); 
} 

main() 
{ 
    Derp x = new Derp(); 
    Foo(ref Derp); 
} 

Esto no se compilará, pero ¿funcionaría?

0

Si utiliza ejemplos prácticos para sus tipos, podrás verlo:

SqlConnection connection = new SqlConnection(); 
Foo(ref connection); 

Y ahora usted tiene su función que toma el ancestro (es decir Object):

void Foo2(ref Object connection) { } 

¿Qué puede estar mal con eso?

void Foo2(ref Object connection) 
{ 
    connection = new Bitmap(); 
} 

Sólo lograron asignar un Bitmap a su SqlConnection.

Eso no es bueno.


intente de nuevo con los demás:

SqlConnection conn = new SqlConnection(); 
Foo2(ref conn); 

void Foo2(ref DbConnection connection) 
{ 
    conn = new OracleConnection(); 
} 

Se rellena un OracleConnection sobre-tapa de su SqlConnection.

Cuestiones relacionadas