Pregunta

Toma lo siguiente:

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é ocurre el error de compilación anterior? Esto sucede con los argumentos ref y out .

¿Fue útil?

Solución

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

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?

Vea la página del blog para más comentarios sobre este tema. Gracias por la gran pregunta.

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

Supongamos que tienes clases Animal , Mammal , Reptile , Giraffe , Turtle y Tiger , con las relaciones obvias de subclasificación.

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


  

¿Puedes pasar una variable de tipo Animal a M ?

No. Esa variable podría contener un Turtle , pero M asumirá que solo contiene mamíferos. Un Turtle no es un Mammal .

Conclusión 1 : los parámetros de ref 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 en m , y M puede escribir un Tiger en m . Ahora ha puesto un Tiger en una variable que en realidad es de tipo Giraffe .

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


Ahora considere N (out Mammal n) .

  

¿Se puede pasar una variable de tipo Giraffe a N ?

No. N puede escribir en n , y N puede escribir un Tiger .

Conclusión 3 : los parámetros de out no se pueden hacer " más pequeños " ;.


  

¿Puedes pasar una variable de tipo Animal a N ?

Hmm.

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

Mal. La regla no es que " N solo puede escribir en n " ;.

Las reglas son, brevemente:

1) N tiene que escribir en n antes de que N devuelva normalmente. (Si N se lanza, todas las apuestas están desactivadas.)

2) N tiene que escribir algo en n antes de leer algo de n .

Eso permite esta secuencia de eventos:

  • Declare un campo x de tipo Animal .
  • Pase x como un parámetro de out 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 cree que es una variable de tipo Mammal .

Claramente queremos hacer eso ilegal.

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


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

Si le interesan estos temas de la teoría de tipos básicos, considere leer mi serie sobre cómo funcionan la covarianza y la contravarianza en C # 4.0 .

Otros consejos

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

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

B b = new A();

Estás luchando con el problema clásico de OOP de covarianza (y contravarianza), consulta wikipedia : por mucho que este hecho pueda 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 elementos son asignables, para por el mismo motivo) al mismo tiempo que respeta principio de Liskov . Por qué esto es así, se esboza en las respuestas existentes y se explora con mayor profundidad en estos artículos de la wiki y sus enlaces.

Los lenguajes OOP que parecen hacerlo mientras se mantienen tradicionalmente estáticamente seguros para tipos son " engaño " (insertando controles de tipo dinámico 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 el desconcierto de los practicantes (como lo hace C # aquí), o pasar a un enfoque de escritura dinámica (como el primer lenguaje OOP, Smalltalk, hizo), o pasar al modo inmutable (individual) datos de asignación), al igual que los lenguajes funcionales (bajo la inmutabilidad, puede admitir la covarianza y también evitar otros rompecabezas relacionados, como el hecho de que no puede tener un rectángulo de subclase Cuadrado en un mundo de datos mutables).

Considera:

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

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

B b = null;
Foo2(ref b);

Violaría la seguridad de tipos

Mientras que las otras respuestas han explicado sucintamente el razonamiento detrás de este comportamiento, creo que vale la pena mencionar que si realmente necesitas hacer algo de esta naturaleza, puedes lograr una funcionalidad similar haciendo de Foo2 un método genérico, como tal:

class A {}

class B : A {}

class C
{
    C()
    {
        var b = new B();
        Foo(b);
        Foo2(ref b); // <= no compile error!
    }

    void Foo(A a) {}

    void Foo2<AType> (ref AType a) where AType: A {}  
}

Porque dar a Foo2 una ref B resultaría en un objeto con formato incorrecto porque Foo2 solo sabe cómo rellenar A parte de B .

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

Foo2(ref (A)b)

Tiene sentido desde una perspectiva de seguridad, pero lo hubiera preferido si el compilador diera una advertencia en lugar de un error, ya que hay usos legítimos de objetos polimoprásicos pasados ??por referencia. por ejemplo

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á?

Si usa ejemplos prácticos para sus tipos, lo verá:

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

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

void Foo2(ref Object connection) { }

¿Qué puede estar mal con eso?

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

Acabas de asignar un Bitmap a tu SqlConnection .

Eso no es bueno.


Inténtalo de nuevo con los demás:

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

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

Usted rellenó un OracleConnection sobre la parte superior de su SqlConnection .

En mi caso, mi función aceptó un objeto y no pude enviar nada, así que simplemente lo hice

object bla = myVar;
Foo(ref bla);

Y eso funciona

Mi 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

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top