=============
AKTUALIZACJA: Użyłem tej odpowiedzi jako podstawy dla tego wpisu na blogu:
Dlaczego parametry ref i out nie pozwalają na zmianę typu?
Więcej komentarzy na ten temat znajdziesz na blogu. Dzięki za świetne pytanie.
=============
Załóżmy, że masz zajęcia Animal
, Mammal
, Reptile
, Giraffe
, Turtle
i Tiger
, z oczywistych relacji podklasy.
Teraz przypuśćmy, że masz metodę void M(ref Mammal m)
. M
potrafi zarówno czytać, jak i pisać m
.
Czy możesz przekazać zmienną typu Animal
do M
?
Nie. Ta zmienna może zawierać a Turtle
, ale M
zakłada, że zawiera tylko ssaki. A Turtle
nie jest Mammal
.
Wniosek 1 : ref
parametrów nie można „zwiększyć”. (Jest więcej zwierząt niż ssaków, więc zmienna staje się „większa”, ponieważ może zawierać więcej rzeczy).
Czy możesz przekazać zmienną typu Giraffe
do M
?
Nie. M
Może pisać do m
i M
może chcieć napisać Tiger
do m
. Teraz wstawiłeś a Tiger
do zmiennej, która jest faktycznie typu Giraffe
.
Wniosek 2 : ref
parametrów nie można uczynić „mniejszymi”.
A teraz zastanów się N(out Mammal n)
.
Czy możesz przekazać zmienną typu Giraffe
do N
?
Nie. N
Może pisać do n
i N
może chcieć napisać Tiger
.
Wniosek 3 : out
parametrów nie można uczynić „mniejszymi”.
Czy możesz przekazać zmienną typu Animal
do N
?
Hmm.
Czemu nie? N
nie może czytać n
, może tylko pisać, prawda? Piszesz a Tiger
do zmiennej typu Animal
i wszystko gotowe, prawda?
Źle. Zasada nie brzmi: „ N
można tylko pisać n
”.
Zasady są w skrócie:
1) N
musi napisać n
przed N
powrotem normalnie. (Jeśli N
rzuca, wszystkie zakłady są anulowane.)
2) N
musi coś napisać, n
zanim coś przeczyta n
.
To pozwala na taką sekwencję wydarzeń:
- Zadeklaruj pole
x
typu Animal
.
- Przekaż
x
jako out
parametr do N
.
N
zapisuje Tiger
do n
, który jest aliasem dla x
.
- W innym wątku ktoś pisze
Turtle
do x
.
N
próbuje odczytać zawartość n
i Turtle
znajduje zmienną typu, jak uważa Mammal
.
Oczywiście chcemy uczynić to nielegalnym.
Wniosek 4 : out
parametrów nie można uczynić „większymi”.
Wniosek końcowy : Ani parametry, ref
ani out
parametry nie mogą różnić się typami. W przeciwnym razie należy złamać weryfikowalne bezpieczeństwo typów.
Jeśli te kwestie w podstawowej teorii typów Cię interesują, rozważ przeczytanie mojej serii o tym, jak działają kowariancja i kontrawariancja w C # 4.0 .
out
nie można zwiększyć parametrów? Opisaną sekwencję można zastosować do dowolnej zmiennej, nie tylkoout
zmiennej parametrycznej. A także czytelnik musi rzucić wartość argumentu,Mammal
zanim spróbuje uzyskać do niego dostęp,Mammal
i oczywiście może się nie powieść, jeśli nie jest rozważnyPonieważ w obu przypadkach musisz mieć możliwość przypisania wartości do parametru ref / out.
Jeśli spróbujesz przekazać b do metody Foo2 jako odniesienie, aw Foo2 spróbujesz przypisać a = new A (), byłoby to nieprawidłowe.
Z tego samego powodu, dla którego nie możesz pisać:
źródło
Zmagasz się z klasycznym problemem OOP kowariancji (i kontrawariancji), patrz wikipedia : chociaż ten fakt może przeciwstawiać się intuicyjnym oczekiwaniom, matematycznie niemożliwe jest zezwolenie na zastępowanie klas pochodnych zamiast podstawowych dla zmiennych (przypisywalnych) argumentów (i także pojemniki, których przedmioty można przypisać z tego samego powodu), przy jednoczesnym poszanowaniu zasady Liskova . Dlaczego tak jest, zostało naszkicowane w istniejących odpowiedziach i dokładniej zbadane w tych artykułach wiki i linkach do nich.
Języki OOP, które wydają się to robić, pozostając tradycyjnie statycznie bezpiecznymi typami, to „oszukiwanie” (wstawianie ukrytych dynamicznych kontroli typów lub wymaganie sprawdzenia WSZYSTKICH źródeł w czasie kompilacji); Podstawowym wyborem jest: albo zrezygnuj z tej kowariancji i zaakceptuj zdziwienie praktyków (tak jak robi to C # tutaj), albo przejdź do dynamicznego podejścia do pisania (jak zrobił to pierwszy język OOP, Smalltalk), albo przejdź do niezmienności (jedno- przypisanie), podobnie jak języki funkcjonalne (w niezmienności można wspierać kowariancję, a także unikać innych powiązanych zagadek, takich jak fakt, że nie można mieć prostokątnej podklasy Square w świecie zmiennych danych).
źródło
Rozważać:
Naruszyłoby to bezpieczeństwo typów
źródło
var
było całkowicie błędne. Naprawiony.Podczas gdy inne odpowiedzi zwięźle wyjaśniły powody tego zachowania, myślę, że warto wspomnieć, że jeśli naprawdę potrzebujesz zrobić coś takiego, możesz osiągnąć podobną funkcjonalność, przekształcając Foo2 w metodę ogólną, jako taką:
źródło
Ponieważ podanie
Foo2
aref B
spowodowałoby zniekształcenie obiektu, ponieważFoo2
wie tylko, jak wypełnićA
częśćB
.źródło
Czy nie jest to, że kompilator mówi ci, że chciałby, abyś jawnie rzutował obiekt, aby mieć pewność, że wiesz, jakie są twoje zamiary?
źródło
Ma to sens z punktu widzenia bezpieczeństwa, ale wolałbym, aby kompilator dał ostrzeżenie zamiast błędu, ponieważ istnieją uzasadnione zastosowania obiektów polimoficznych przekazywanych przez odniesienie. na przykład
To się nie skompiluje, ale czy zadziała?
źródło
Jeśli użyjesz praktycznych przykładów dla swoich typów, zobaczysz to:
A teraz masz swoją funkcję, która przyjmuje przodka ( tj.
Object
):Co może być w tym złego?
Właśnie udało Ci się przypisać
Bitmap
do swojegoSqlConnection
.To nie dobrze.
Spróbuj ponownie z innymi:
Wypchałeś
OracleConnection
górną część swojegoSqlConnection
.źródło
W moim przypadku moja funkcja przyjęła obiekt i nie mogłem nic wysłać, więc po prostu to zrobiłem
I to działa
Mój Foo jest w VB.NET i sprawdza typ wewnątrz i wykonuje dużo logiki
Przepraszam, jeśli moja odpowiedź jest podwójna, ale inne były zbyt długie
źródło