123 votes

C# : Pourquoi 'ref' et 'out' ne supportent-ils pas le polymorphisme ?

Prenez les éléments suivants :

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 deux ref et out arguments.

168voto

Eric Lippert Points 300275

\=============

MISE À JOUR : J'ai utilisé cette réponse comme base pour cet article de blog :

Pourquoi les paramètres ref et out ne permettent-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 des classes Animal , Mammal , Reptile , Giraffe , Turtle et Tiger avec les relations de sous-classement évidentes.

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


Peut-on passer une variable de type Animal à M ?

Non. Cette variable pourrait contenir un Turtle mais M supposera qu'il ne contient que des Mammifères. A Turtle n'est pas un Mammal .

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


Peut-on passer une variable de type Giraffe à M ?

Non. M peut écrire à m et M pourrait vouloir écrire un Tiger en m . Maintenant vous avez mis un Tiger dans une variable qui est en fait de type Giraffe .

Conclusion 2 : ref Les paramètres ne peuvent pas être "réduits".


Considérons maintenant N(out Mammal n) .

Peut-on passer une variable de type Giraffe à N ?

Non. N peut écrire à n et N pourrait vouloir écrire un Tiger .

Conclusion 3 : out Les paramètres ne peuvent pas être "réduits".


Peut-on passer une variable de type Animal à N ?

Hmm.

Eh bien, pourquoi pas ? N ne peut pas lire de n il ne peut qu'écrire dessus, non ? Vous écrivez un Tiger à une variable de type Animal et tu es prêt, n'est-ce pas ?

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

Les règles sont, brièvement :

1) N doit écrire à n avant N revient normalement. (Si N jets, tous les paris sont ouverts).

2) N doit écrire quelque chose pour n avant de lire quelque chose de n .

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

  • Déclarer un champ x de type Animal .
  • Passez x comme un out pour N .
  • N écrit un Tiger en n qui est un alias pour x .
  • Sur un autre fil, quelqu'un écrit un Turtle en x .
  • N tente de lire le contenu de n et découvre une Turtle dans ce qu'il pense être une variable de type Mammal .

Il est clair que nous voulons rendre cela illégal.

Conclusion 4 : out les paramètres ne peuvent pas être "agrandis".


Conclusion finale : Ni l'un ni l'autre ref ni out Les paramètres peuvent varier selon leur type. Faire autrement revient à rompre la sécurité vérifiable des types.

Si ces questions relatives à la théorie des types de base vous intéressent, envisagez la lecture des documents suivants ma série sur le fonctionnement de la covariance et de la contravariance en C# 4.0 .

29voto

maciejkow Points 4504

Car dans les deux cas, vous devez pouvoir attribuer une valeur au paramètre ref/out.

Si vous essayez de passer b dans la méthode Foo2 comme référence, et dans Foo2 vous essayez d'assigner a = new A(), ce serait invalide.
Pour la même raison que tu ne sais pas écrire :

B b = new A();

10voto

Alex Martelli Points 330805

Vous vous débattez avec le problème classique de la POO, à savoir covariance (et la contravariance), voir wikipedia : bien que ce fait puisse défier les attentes intuitives, il est mathématiquement impossible de permettre la substitution des classes dérivées au lieu des classes de base pour les arguments mutables (assignables) (et aussi les conteneurs dont les éléments sont assignables, pour la même raison) tout en respectant Le principe de Liskov . La raison pour laquelle il en est ainsi est esquissée dans les réponses existantes, et explorée plus profondément dans ces articles wiki et leurs liens.

Les langages de programmation orientée objet qui semblent le faire tout en restant traditionnellement statiquement sûrs en matière de typage "trichent" (en insérant des contrôles de type dynamiques cachés, ou en exigeant un examen à la compilation de TOUTES les sources pour les vérifier) ; le choix fondamental est le suivant : soit abandonner cette covariance et accepter la perplexité des praticiens (comme le fait C# ici), soit passer à une approche de typage dynamique (comme l'a fait le tout premier langage POO, Smalltalk), soit passer à des données immuables (à affectation unique), comme le font les langages fonctionnels (sous immuabilité, vous pouvez supporter la covariance, et aussi éviter d'autres énigmes connexes telles que le fait que vous ne pouvez pas avoir Square sous-classe Rectangle dans un monde de données mutables).

4voto

Henk Holterman Points 153608

Pensez-y :

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

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

var b = null;
Foo2(ref b);

Cela violerait la sécurité des types

2voto

CannibalSmith Points 1768

Parce que donner Foo2 a ref B donnerait lieu à un objet malformé car Foo2 ne sait que remplir A partie de B .

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X