Question

Prenez ce qui suit:

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) {}  
}

Pourquoi l’erreur de compilation ci-dessus se produit-elle? Cela se produit avec les arguments ref et out .

Était-ce utile?

La solution

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

UPDATE: j'ai utilisé cette réponse comme base pour cette entrée de blog:

Pourquoi les paramètres ref et out n'autorisent-ils pas la variation de type?

Voir la page du blog pour plus de commentaires sur cette question. Merci pour cette excellente question.

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

Supposons que vous ayez les classes Animal , Mammifère , Reptile , Giraffe , Tortue et Tiger , avec les relations évidentes de sous-classes.

Supposons maintenant que vous avez une méthode void M (réf Mammal m) . M peut lire et écrire à la fois m .

  

Pouvez-vous passer une variable de type Animal à M ?

Non. Cette variable pourrait contenir un Tortue , mais M supposera qu'elle ne contient que des mammifères. Un tortue n'est pas un mammifère .

Conclusion 1 : Les paramètres ref ne peuvent pas être rendus "plus grands". (Il y a plus d'animaux que de mammifères, donc la variable devient "plus grosse" car elle peut contenir plus de choses.)

  

Pouvez-vous passer une variable de type Giraffe à M ?

Non. M peut écrire dans m et M peut vouloir écrire un Tiger dans m . Vous avez maintenant ajouté un Tiger dans une variable qui est en fait de type Giraffe .

Conclusion 2 : Les paramètres ref ne peuvent pas être rendus "plus petits".

Considérons maintenant N (out Mammal n) .

  

Pouvez-vous passer une variable de type Giraffe à N ?

Non. N peut écrire dans n et N peut vouloir écrire un Tiger .

Conclusion 3 : Les paramètres out ne peuvent pas être rendus "plus petits".

  

Pouvez-vous passer une variable de type Animal à N ?

Hmm.

Eh bien, pourquoi pas? N ne peut pas lire dans n , il ne peut y écrire que, n'est-ce pas? Vous écrivez un Tiger dans une variable de type Animal et vous êtes prêt, n'est-ce pas?

Faux. La règle n'est pas " N ne peut écrire que dans n ".

Les règles sont, brièvement:

1) N doit écrire dans n avant que N ne soit renvoyé normalement. (Si N est lancé, tous les paris sont désactivés.)

2) N doit écrire quelque chose sur n avant de pouvoir lire quelque chose dans n .

Cela permet cette séquence d'événements:

  • Déclarez un champ x de type Animal .
  • Transmettez x en tant que paramètre out à N .
  • N écrit un Tiger dans n , qui est un alias pour x .
  • Sur un autre sujet, quelqu'un écrit un Tortue dans x .
  • N tente de lire le contenu de n et découvre une Tortue dans ce qu'elle pense être une variable de type Mammal .

Clairement, nous voulons rendre cela illégal.

Conclusion 4 : Les paramètres out ne peuvent pas être rendus "plus grands".

Conclusion finale : Les paramètres ref ni out ne peuvent en modifier le type. Faire autrement, c'est casser la sécurité de type vérifiable.

Si ces questions vous intéressent dans la théorie des types de base, pensez à lire ma série sur la covariance et la contravariance en C # 4.0 .

Autres conseils

Parce que dans les deux cas, vous devez pouvoir affecter une valeur au paramètre ref / out.

Si vous essayez de passer b dans la méthode Foo2 en tant que référence et que vous essayez d'associer a = new A (), cela ne sera pas valide.
Même raison pour laquelle vous ne pouvez pas écrire:

B b = new A();

Vous vous débattez avec le problème classique de la POO de covariance (et de contravariance), voir wikipedia : bien que ce fait puisse défier les attentes intuitives, il est mathématiquement impossible d'autoriser la substitution de classes dérivées à la place de classes de base pour des arguments mutables (assignables) (et des conteneurs dont les éléments sont assignables, par exemple). pour la même raison) tout en respectant le principe de Liskov . La raison en est telle qu’elle est esquissée dans les réponses existantes et explorée plus en profondeur dans ces articles de wiki et leurs liens.

Les langues POO qui semblent le faire, tout en restant traditionnellement statiques, sont "triche". (insertion de vérifications de type dynamiques masquées ou nécessité d'un examen de TOUTES les sources à la compilation); le choix fondamental est le suivant: abandonner cette covariance et accepter l’énigme des praticiens (comme C # le fait ici), ou passer à une approche de frappe dynamique (comme le tout premier langage POO, Smalltalk, l’a fait), ou passer à une méthode immuable (simple). comme les langages fonctionnels (sous immutabilité, vous pouvez prendre en charge la covariance et éviter d’autres énigmes telles que le fait que vous ne pouvez pas avoir la sous-classe Square Rectangle dans un monde de données mutable).

Considérez:

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

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

B b = null;
Foo2(ref b);

Cela violerait la sécurité de type

Alors que les autres réponses ont expliqué de manière succincte le raisonnement derrière ce comportement, je pense qu’il est utile de mentionner que si vous devez vraiment faire quelque chose de ce genre, vous pouvez obtenir une fonctionnalité similaire en transformant Foo2 en une méthode générique, en tant que telle:

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 {}  
}

Parce que donner à Foo2 un ref B donnerait un objet mal formé, car Foo2 sait seulement comment remplir A partie de B .

N’est-ce pas le compilateur qui vous dit qu’il aimerait que vous convertissiez explicitement l’objet afin qu’il sache quelles sont vos intentions?

Foo2(ref (A)b)

Cela a du sens du point de vue de la sécurité, mais je l’aurais préféré si le compilateur donnait un avertissement au lieu d’une erreur, car il existe des utilisations légitimes d’objets polymorphes passés par référence. par exemple

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);
}

Cela ne compilera pas, mais est-ce que cela fonctionnerait?

Si vous utilisez des exemples pratiques pour vos types, vous le verrez:

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

Et vous avez maintenant votre fonction qui prend l'ancêtre ( i.e. Objet ):

void Foo2(ref Object connection) { }

Qu'est-ce qui ne va peut-être pas avec ça?

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

Vous venez juste d’attribuer un Bitmap à votre SqlConnection .

Ce n'est pas bon.

Réessayez avec les autres:

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

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

Vous avez bourré un OracleConnection au-dessus de votre SqlConnection .

Dans mon cas, ma fonction a accepté un objet et je ne pouvais rien envoyer alors j'ai tout simplement

object bla = myVar;
Foo(ref bla);

Et ça marche

Mon Foo est dans VB.NET. Il vérifie le type à l'intérieur et fait beaucoup de logique

Je m'excuse si ma réponse est en double mais que d'autres étaient trop longues

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top