Warum nicht 'ref' und 'out' support-Polymorphismus?
-
05-07-2019 - |
Frage
Nehmen Sie die folgenden:
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) {}
}
Warum hat die oben compile-Zeit-Fehler auftreten?Dies geschieht mit den beiden ref
und out
Argumente.
Lösung
=============
UPDATE:Ich habe diese Antwort als Grundlage für diesen blog-Eintrag:
Warum ref-und out-Parameter nicht zulassen, dass Typ-Veränderung?
Siehe die blog-Seite für weitere Kommentar zu diesem Thema.Vielen Dank für die tolle Frage.
=============
Angenommen, Sie haben Klassen Animal
, Mammal
, Reptile
, Giraffe
, Turtle
und Tiger
, mit der offensichtlichen Unterklassen-Beziehungen.
Nehmen wir nun an, Sie haben eine Methode void M(ref Mammal m)
. M
kann sowohl Lesen und schreiben m
.
Übergeben Sie eine variable des Typs
Animal
zuM
?
Nein.Diese variable könnte enthalten ein Turtle
, aber M
wird davon ausgegangen, dass es enthält nur Säugetiere.Ein Turtle
ist nicht ein Mammal
.
Fazit 1: ref
Parameter können nicht gemacht werden "größer".(Es gibt mehr Tiere als Säugetiere, so wird die variable wird immer "größer", denn es kann enthalten mehr Sachen.)
Übergeben Sie eine variable des Typs
Giraffe
zuM
?
Nein. M
schreiben kann m
, und M
vielleicht schreiben möchten Tiger
in m
.Jetzt Sie haben eine Tiger
in eine variable, das ist eigentlich der Typ Giraffe
.
Fazit 2: ref
Parameter können nicht gemacht werden "kleinere".
Betrachten wir nun N(out Mammal n)
.
Übergeben Sie eine variable des Typs
Giraffe
zuN
?
Nein. N
schreiben kann n
, und N
vielleicht schreiben möchten Tiger
.
Fazit 3: out
Parameter können nicht gemacht werden "kleinere".
Übergeben Sie eine variable des Typs
Animal
zuN
?
Hmm.
Nun, warum nicht? N
nicht gelesen werden n
, es kann nur schreiben, richtig?Sie schreiben ein Tiger
um eine variable des Typs Animal
und Sie sind alle eingestellt, richtig?
Falsch.Die Regel ist das nicht "N
kann nur schreiben n
".
Die Regeln sind kurz:
1) N
hat zu schreiben n
bevor N
returns normal.(Wenn N
wirft, sind alle Wetten sind aus.)
2) N
zu schreiben, hat etwas zu n
bevor es liest etwas aus n
.
Ermöglicht dies die Abfolge der Ereignisse:
- Deklarieren Sie ein Feld
x
TypAnimal
. - Pass
x
alsout
parameterN
. N
schreibt einTiger
inn
,, das ist ein alias fürx
.- In einem anderen thread, jemand schreibt ein
Turtle
inx
. N
versuche, den Inhalt zu Lesen dern
, und entdeckt einTurtle
in dem, was es denkt, ist eine variable des TypsMammal
.
Klar, wir wollen, dass die illegalen.
Fazit 4: out
Parameter können nicht gemacht werden "größer".
Abschließendes Fazit: Weder ref
noch out
die Parameter können variieren, Ihre Typen.Alles andere ist eine Pause überprüfbare Art Sicherheit.
Wenn Sie diese Fragen im basic-Typ-Theorie, die Sie interessieren, Lesen Sie meine Serie auf, wie Kovarianz und Kontravarianz Arbeit in C# 4.0.
Andere Tipps
Da in beiden Fällen müssen Sie in der Lage sein Wert zuweisen / out-Parameter ref.
Wenn Sie versuchen, b in foo2 Verfahren als Referenz zu übergeben, und in foo2 Sie versuchen, eine = new A bis assing (), wäre dies ungültig.
Aus demselben Grund kann man nicht schreiben:
B b = new A();
Sie sind mit dem klassischen OOP Problem der Kovarianz (und Kontra) kämpfen haben, finden Sie unter wikipedia : viel wie dieser Tatsache kann intuitive Erwartungen trotzen, es mathematisch unmöglich ist, die Substitution von abgeleiteten Klassen anstelle von Basis diejenigen für wandelbar (belegbar) Argumente (und auch Behälter, deren Elemente sind belegbar zu ermöglichen, für nur aus dem gleichen Grund), während immer noch Liskov Prinzip respektieren. Warum ist das so ist in den bestehenden Antworten skizziert, und erforschte tiefer in diesem Wiki-Artikel und Links davon.
OOP-Sprachen, die dies angezeigt, während traditionell statisch verbleibenden typsicher ist „Betrug“ (Einfügen versteckten dynamische Typprüfungen oder erfordern Kompilierung-Prüfung aller Quellen zu überprüfen); die grundsätzliche Wahl: entweder auf dieser Kovarianz aufgeben und Verblüffung der Praktizierenden akzeptieren (wie C # hier der Fall ist), oder in einen dynamischen Typisierung Ansatz bewegen (als allererstes OOP-Sprache, Smalltalk, tat) oder unveränderlich bewegen (Single- Zuordnung) Daten, wie funktionale Sprachen (unter Unveränderlichkeit tun, können Sie Kovarianz unterstützen, und auch andere damit zusammenhängende Rätsel wie die Tatsache vermeiden, dass Sie nicht Platz Unterklasse Rechteck in einer wandelbaren-Datenwelt haben können).
Bedenken Sie:
class C : A {}
class B : A {}
void Foo2(ref A a) { a = new C(); }
B b = null;
Foo2(ref b);
Es würde verletzen Typsicherheit
Während die anderen Antworten haben kurz und bündig die Gründe für dieses Verhalten erklärt, ich glaube, es wert ist zu erwähnen, dass, wenn Sie wirklich etwas dieser Art tun müssen, um Sie, indem foo2 in eine generische Methode ähnliche Funktionalität erreichen können, wie zum Beispiel:
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 {}
}
Da ein Foo2
ref B
gibt in einem fehlerhaften Objekt führen würde, weil Foo2
nur weiß, wie A
Teil B
zu füllen.
Ist das nicht der Compiler sagen Sie es möchten Sie ausdrücklich um das Objekt zu werfen, so dass es sicher sein kann, Sie wissen, was Ihre Absichten sind?
Foo2(ref (A)b)
Das macht Sinn aus sicherheitstechnischer Sicht, aber ich hätte es vorgezogen, wenn der Compiler eine Warnung statt einem Fehler gibt, da es rechtmäßige Nutzung der polymoprhic Objekte als Referenz übergeben. z.
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);
}
Dies wird nicht kompilieren, aber es funktionieren würde?
Wenn Sie praktische Beispiele für die Typen verwenden, werden Sie es sehen:
SqlConnection connection = new SqlConnection();
Foo(ref connection);
Und jetzt haben Sie Ihre Funktion, die die Vorfahren nimmt ( das heißt Object
.):
void Foo2(ref Object connection) { }
Was möglicherweise daran falsch sein?
void Foo2(ref Object connection)
{
connection = new Bitmap();
}
Sie schaffte es gerade noch eine Bitmap
zu Ihrem SqlConnection
zuzuweisen.
Das ist nicht gut.
Versuchen Sie es erneut mit anderen:
SqlConnection conn = new SqlConnection();
Foo2(ref conn);
void Foo2(ref DbConnection connection)
{
conn = new OracleConnection();
}
Sie stopfte eine OracleConnection
über oben auf Ihrem SqlConnection
.
In meinem Fall meine Funktion ein Objekt akzeptiert und ich kann an nichts senden, damit ich einfach tat
object bla = myVar;
Foo(ref bla);
Und das funktioniert
Meine Foo sind in VB.NET und prüft nach Art innerhalb und macht eine Menge Logik
Ich entschuldige mich, wenn meine Antwort Duplikat ist aber andere waren zu lang