polimorfismo herencia hace encapsulamiento ejemplos como clases c# polymorphism out-parameters ref-parameters

herencia - polimorfismo c#



¿Por qué ''ref'' y ''afuera'' no apoyan el polimorfismo? (9)

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

Foo2(ref (A)b)

Tome 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 tiempo de compilación anterior? Esto ocurre con los argumentos de ref y out .


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

ACTUALIZACIÓN: utilicé esta respuesta como base para esta entrada de blog:

¿Por qué los parámetros de 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 tiene clases Animal , Mammal , Reptile , Giraffe , Turtle y Tiger , con las obvias relaciones de subclases.

Ahora supongamos 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 una Turtle , pero M asumirá que solo contiene Mamíferos. Una Turtle no es un Mammal .

Conclusión 1 : los parámetros de ref no se pueden hacer "más grandes". (Hay más animales que mamíferos, por lo que la variable se está "agrandando" porque puede contener más cosas).

¿Puedes pasar una variable de tipo Giraffe a M ?

No. M puede escribir en m , y M puede querer escribir un Tiger en m . Ahora has puesto un Tiger en una variable que en realidad es del tipo Giraffe .

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

Ahora considere N(out Mammal n) .

¿Puedes pasar una variable de tipo Giraffe a N ?

No. N puede escribirle a n , y N puede querer escribir un Tiger .

Conclusión 3 : out parámetros 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, ¿no? Escribes un Tiger a una variable de tipo Animal y estás listo, ¿verdad?

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

Las reglas son, brevemente:

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

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

Eso permite esta secuencia de eventos:

  • Declara 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 una Turtle en x .
  • N intenta leer el contenido de n , y descubre una Turtle en lo que cree que es una variable de tipo Mammal .

Claramente, queremos que eso sea ilegal.

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

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

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


Considerar:

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 tipo


Estás luchando con el problema OOP clásico de covarianza (y contravariancia), ver 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 para argumentos mutables (asignables) (y también contenedores cuyos artículos son asignables, por la misma razón) mientras se respeta el principio de Liskov . 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 de OOP que parecen hacerlo mientras permanecen tradicionalmente seguras en cuanto a tipo de seguridad son "trampas" (insertar comprobaciones dinámicas ocultas o requerir 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), al igual que 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).


Mientras que las otras respuestas explicaron 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 convirtiendo a Foo2 en 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 Foo2 a ref B daría como resultado un objeto malformado porque Foo2 solo sabe cómo llenar A parte de B


Porque en ambos casos debe poder asignar 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();


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

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

Y ahora tienes tu función que toma el antecesor ( es decir, el Object ):

void Foo2(ref Object connection) { }

¿Qué puede estar mal con eso?

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

Acabas de asignar un Bitmap de 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(); }

OracleConnection un OracleConnection por encima de tu SqlConnection .


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 polimórficos 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?