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 deux arguments ref
et out
.
=============
UPDATE: J'ai utilisé cette réponse comme base pour cette entrée 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 la bonne question.
=============
Supposons que vous avez des classes Animal
, Mammal
, Reptile
, Giraffe
, Turtle
et Tiger
, avec les relations évidentes de sous-classes.
Supposons maintenant que vous avez une méthode void M(ref Mammal m)
. M
peut à la fois lire et écrire m
.
Pouvez-vous passer une variable de type
Animal
àM
?
Non. Cette variable peut contenir une Turtle
, mais M
supposera qu'elle ne contient que des mammifères. Une Turtle
n'est pas une Mammal
.
Les paramètres Conclusion 1: ref
ne peuvent pas être "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.)
Pouvez-vous passer une variable de type
Giraffe
àM
?
M
peut écrire à m
et M
voudra peut-être écrire un Tiger
dans m
. Maintenant vous avez mis une Tiger
dans une variable qui est en fait de type Giraffe
.
Les paramètres Conclusion 2: ref
ne peuvent pas être "plus petits".
Maintenant, considérons N(out Mammal n)
.
Pouvez-vous passer une variable de type
Giraffe
àN
?
N
peut écrire à n
et N
voudra peut-être écrire un Tiger
.
Les paramètres Conclusion 3: out
ne peuvent pas être "plus petits".
Pouvez-vous passer une variable de type
Animal
àN
?
Hmm.
Eh bien pourquoi pas? N
ne peut pas lire à partir de n
, il ne peut que lui écrire, non? Vous écrivez une Tiger
dans une variable de type Animal
et vous êtes tous ensemble, 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 à n
avant que N
soit retourné normalement. (Si N
jette, tous les paris sont off.)
2) N
doit écrire quelque chose dans n
avant de pouvoir lire quelque chose dans n
.
Cela permet cette séquence d'événements:
x
de type Animal
.x
en tant que paramètre out
à N
. N
écrit une Tiger
dans n
, qui est un alias pour x
. Turtle
dans x
. N
tente de lire le contenu de n
et découvre une Turtle
dans ce qu’elle considère être une variable de type Mammal
.Clairement, nous voulons rendre cela illégal.
Les paramètres Conclusion 4: out
ne peuvent pas être agrandis.
Conclusion finale: Ni les paramètres ref
ni out
ne peuvent en changer les types. Autrement, 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 .
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'assimiler a = new A (), cela sera invalide.
Même raison pour laquelle vous ne pouvez pas écrire:
B b = new A();
Vous vous débattez avec le classique OOP - problème de covariance (et de contravariance), voir wikipedia : bien que ce fait puisse défier les attentes intuitives, il est mathématiquement impossible de permettre la substitution de classes au lieu de celles 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 . Pourquoi est-ce ainsi que cela est esquissé dans les réponses existantes et exploré plus en profondeur dans ces articles de wiki et leurs liens.
Les langages POO qui semblent le faire, tout en restant traditionnellement statiques, sont "tricheurs" (insertion de vérifications de type dynamiques cachées, ou nécessitant un examen au moment de la compilation de TOUTES LES sources à vérifier); le choix fondamental est le suivant: soit abandonner cette covariance et accepter le mystère des praticiens (comme C # l’a fait ici), soit adopter une approche de frappe dynamique (comme le tout premier OOP langage, Smalltalk, l’a fait), ou déplacez-vous vers les données immuables (affectation unique), comme le font les langages fonctionnels (sous Immutability, vous pouvez prendre en charge la covariance et éviter d'autres énigmes connexes, comme le fait que vous ne pouvez pas avoir la sous-classe Square Rectangle dans un monde de données mutables).
Considérer:
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
Parce que donner Foo2
un ref B
donnerait un objet mal formé parce que Foo2
sait seulement comment remplir A
une partie de B
.
Alors que les autres réponses ont expliqué succinctement le raisonnement derrière ce comportement, je pense qu’il est utile de mentionner que si vous devez réellement 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 {}
}
Si vous utilisez des exemples pratiques pour vos types, vous le verrez:
SqlConnection connection = new SqlConnection();
Foo(ref connection);
Et maintenant vous avez votre fonction qui prend le ancestor (i.e.Object
):
void Foo2(ref Object connection) { }
Qu'est-ce qui peut éventuellement être faux avec ça?
void Foo2(ref Object connection)
{
connection = new Bitmap();
}
Vous venez juste d’attribuer une Bitmap
à votre SqlConnection
.
Ce n'est pas bon.
Essayez encore avec les autres:
SqlConnection conn = new SqlConnection();
Foo2(ref conn);
void Foo2(ref DbConnection connection)
{
conn = new OracleConnection();
}
Vous avez rempli une OracleConnection
over-top de votre SqlConnection
.
N’est-ce pas le compilateur qui vous dit qu’il aimerait que vous convertissiez explicitement l’objet afin qu’il soit certain de connaître vos intentions?
Foo2(ref (A)b)
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 et 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 ont été trop longues
C’est logique du point de vue de la sécurité, mais j’aurais préféré cela si le compilateur donnait un avertissement au lieu d’une erreur, car il existe des utilisations légitimes des 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 ça marcherait?