Nawet jeśli można je zobaczyć w jakiś sposób za równoważne są zupełnie inne w celu. Najpierw spróbujmy zdefiniować, czym jest obsada:
Rzutowanie to czynność polegająca na zmianie jednostki jednego typu danych na inny.
Jest to trochę ogólne i jest w pewnym sensie równoważne z konwersją, ponieważ rzutowanie często ma taką samą składnię konwersji, więc pytanie powinno brzmieć, kiedy rzutowanie (niejawne lub jawne) jest dozwolone przez język i kiedy trzeba użyć ( więcej) wyraźna konwersja?
Pozwólcie, że najpierw narysuję między nimi prostą linię. Formalnie (nawet jeśli jest to równoważne dla składni języka) rzutowanie zmieni typ, podczas gdy konwersja zmieni / może zmienić wartość (ostatecznie razem z typem). Obsada jest również odwracalna, podczas gdy konwersja może nie być.
Ten temat jest dość obszerny, więc spróbujmy go nieco zawęzić, wykluczając z gry niestandardowych operatorów rzutów.
Niejawne rzuty
W C # rzutowanie jest niejawne, gdy nie stracisz żadnych informacji (pamiętaj, że to sprawdzenie jest wykonywane z typami, a nie z ich rzeczywistymi wartościami ).
Typy prymitywne
Na przykład:
int tinyInteger = 10;
long bigInteger = tinyInteger;
float tinyReal = 10.0f;
double bigReal = tinyReal;
Rzuty te są niejawne, ponieważ podczas konwersji nie stracisz żadnych informacji (po prostu poszerzysz typ). I odwrotnie, niejawne rzutowanie nie jest dozwolone, ponieważ niezależnie od ich rzeczywistych wartości (ponieważ można je sprawdzić tylko w czasie wykonywania), podczas konwersji możesz utracić niektóre informacje. Na przykład ten kod nie zostanie skompilowany, ponieważ a double
może zawierać (i faktycznie ma) wartość, której nie można przedstawić za pomocą float
:
double bigReal = Double.MaxValue;
float tinyReal = bigReal;
Obiekty
W przypadku obiektu (wskaźnika do) rzutowanie jest zawsze niejawne, gdy kompilator może mieć pewność, że typ źródłowy jest klasą pochodną (lub implementuje) typ klasy docelowej, na przykład:
string text = "123";
IFormattable formattable = text;
NotSupportedException derivedException = new NotSupportedException();
Exception baseException = derivedException;
W tym przypadku kompilator wie, że string
implementuje IFormattable
i to NotSupportedException
jest (pochodzi z), Exception
więc rzutowanie jest niejawne. Żadne informacje nie są tracone, ponieważ obiekty nie zmieniają swoich typów (jest inaczej w przypadku struct
s i typów prymitywnych, ponieważ za pomocą rzutowania tworzysz nowy obiekt innego typu ), zmienia się twój widok na nie.
Jawne rzuty
Rzutowanie jest jawne, gdy konwersja nie jest wykonywana niejawnie przez kompilator, a następnie należy użyć operatora rzutowania. Zwykle oznacza to, że:
- Możesz utracić informacje lub dane, więc musisz być tego świadomy.
- Konwersja może się nie powieść (ponieważ nie możesz przekonwertować jednego typu na inny), więc ponownie musisz być świadomy tego, co robisz.
Typy prymitywne
Jawne rzutowanie jest wymagane w przypadku typów pierwotnych, gdy podczas konwersji możesz utracić część danych, na przykład:
double precise = Math.Cos(Math.PI * 1.23456) / Math.Sin(1.23456);
float coarse = (float)precise;
float epsilon = (float)Double.Epsilon;
W obu przykładach, nawet jeśli wartości mieszczą się w float
zakresie, utracisz informacje (w tym przypadku precyzję), więc konwersja musi być jawna. Teraz spróbuj tego:
float max = (float)Double.MaxValue;
Ta konwersja nie powiedzie się, więc ponownie musi być jawna, abyś był tego świadomy i możesz przeprowadzić kontrolę (w przykładzie wartość jest stała, ale może pochodzić z niektórych obliczeń w czasie wykonywania lub operacji we / wy). Wróćmy do przykładu:
string text = "123";
double value = (double)text;
To się nie skompiluje, ponieważ kompilator nie może przekonwertować tekstu na liczby. Tekst może zawierać dowolne znaki, a nie tylko liczby, a to za dużo w C #, nawet w przypadku jawnego rzutowania (ale może być dozwolone w innym języku).
Obiekty
Konwersje ze wskaźników (na obiekty) mogą się nie powieść, jeśli typy są niepowiązane, na przykład ten kod nie zostanie skompilowany (ponieważ kompilator wie, że nie ma możliwej konwersji):
string text = (string)AppDomain.Current;
Exception exception = (Exception)"abc";
Ten kod zostanie skompilowany, ale może się nie powieść w czasie wykonywania (zależy to od efektywnego typu rzutowanych obiektów) z InvalidCastException
:
object obj = GetNextObjectFromInput();
string text = (string)obj;
obj = GetNextObjectFromInput();
Exception exception = (Exception)obj;
Konwersje
W końcu, jeśli rzutowania są konwersjami, to po co nam takie klasy Convert
? Ignorowanie subtelnych różnic, które pochodzą z Convert
implementacji i IConvertible
implementacji, ponieważ w C # z rzutowaniem mówisz do kompilatora:
zaufaj mi, ten typ jest tego typu, nawet jeśli nie możesz tego teraz wiedzieć, pozwól mi to zrobić, a zobaczysz.
-lub-
nie martw się, nie obchodzi mnie, czy coś zostanie utracone podczas tej konwersji.
Do wszystkiego innego potrzebna jest bardziej wyraźna operacja (pomyśl o implikacjach łatwych rzutów , dlatego C ++ wprowadził dla nich długą, pełną i jawną składnię). Może to wiązać się ze złożoną operacją (do konwersji string
-> double
będzie potrzebna analiza). Na przykład konwersja na format string
jest zawsze możliwa ( ToString()
metodą), ale może oznaczać coś innego niż to, czego oczekujesz, więc musi być bardziej wyraźna niż rzut ( więcej piszesz, więcej myślisz o tym, co robisz ).
Ta konwersja może być wykonana wewnątrz obiektu (przy użyciu znanych instrukcji IL), przy użyciu niestandardowych operatorów konwersji (zdefiniowanych w klasie do rzutowania) lub bardziej złożonych mechanizmów ( TypeConverter
na przykład metod lub metod klas). Nie jesteś świadomy tego, co się stanie, ale zdajesz sobie sprawę, że może się to nie udać (dlatego IMO, gdy możliwa jest bardziej kontrolowana konwersja, powinieneś jej użyć). W twoim przypadku konwersja po prostu przeanalizuje, string
tworząc double
:
double value = Double.Parse(aStringVariable);
Oczywiście może się to nie powieść, więc jeśli to zrobisz, zawsze powinieneś złapać wyjątek, który może rzucić ( FormatException
). To jest poza tematem, ale kiedy TryParse
jest dostępne, powinieneś go użyć (ponieważ semantycznie mówisz, że może to nie być liczba, a nawet szybciej ... zawieść).
Konwersje w .NET mogą pochodzić z wielu miejsc, TypeConverter
niejawnych / jawnych rzutów ze zdefiniowanymi przez użytkownika operatorami konwersji, implementacji IConvertible
i analizowania metod (czy o czymś zapomniałem?). Zajrzyj na MSDN, aby uzyskać więcej informacji na ich temat.
Na zakończenie tej długiej odpowiedzi wystarczy kilka słów o operatorach konwersji zdefiniowanych przez użytkownika. To po prostu cukier, aby pozwolić programiście na użycie rzutowania do konwersji jednego typu na inny. Jest to metoda wewnątrz klasy (tej, która zostanie rzutowana), która mówi "hej, jeśli on / ona chce przekonwertować ten typ na ten typ, mogę to zrobić". Na przykład:
float? maybe = 10;
float sure1 = (float)maybe;
float sure2 = maybe.Value;
W tym przypadku jest to wyraźne, ponieważ może się nie powieść, ale jest to dozwolone do implementacji (nawet jeśli istnieją wytyczne na ten temat). Wyobraź sobie, że piszesz niestandardową klasę ciągów w następujący sposób:
EasyString text = "123";
double value = (string)text;
W swojej implementacji możesz zdecydować się na „ułatwienie życia programistom” i ujawnienie tej konwersji poprzez rzutowanie (pamiętaj, że to tylko skrót do pisania mniej). Niektóre języki mogą nawet na to pozwolić:
double value = "123";
Umożliwienie niejawnej konwersji do dowolnego typu (sprawdzenie zostanie wykonane w czasie wykonywania). Przy odpowiednich opcjach można to zrobić na przykład w VB.NET. To po prostu inna filozofia.
Co mogę z nimi zrobić?
Więc ostatnie pytanie brzmi: kiedy powinieneś użyć jednego lub drugiego. Zobaczmy, kiedy możesz użyć wyraźnej obsady:
- Konwersje między typami podstawowymi.
- Konwersje z
object
do dowolnego innego typu (może to obejmować również rozpakowywanie).
- Konwersje z klasy pochodnej do klasy bazowej (lub do zaimplementowanego interfejsu).
- Konwersje z jednego typu na inny za pomocą niestandardowych operatorów konwersji.
Można wykonać tylko pierwszą konwersję, Convert
więc w przypadku innych nie masz wyboru i musisz użyć jawnej obsady.
Zobaczmy teraz, kiedy możesz użyć Convert
:
- Konwersje z dowolnego typu podstawowego na inny typ podstawowy (z pewnymi ograniczeniami, zobacz MSDN ).
- Konwersje z dowolnego typu, który jest implementowany,
IConvertible
na dowolny inny (obsługiwany) typ.
- Konwersje z / na
byte
tablicę na / z ciągu.
Wnioski
IMO Convert
powinno być używane za każdym razem, gdy wiesz, że konwersja może się nie powieść (ze względu na format, zakres lub może być nieobsługiwany), nawet jeśli tę samą konwersję można wykonać za pomocą rzutowania (chyba że jest dostępne coś innego). Wyjaśnia, kto będzie czytał Twój kod, jaki jest Twój zamiar i że może się nie powieść (uproszczenie debugowania).
Do wszystkiego innego potrzebujesz odlewu, nie ma wyboru, ale jeśli dostępna jest inna lepsza metoda, sugeruję jej użycie. W twoim przykładzie konwersja z string
do double
jest czymś, co (zwłaszcza jeśli tekst pochodzi od użytkownika) bardzo często kończy się niepowodzeniem, więc powinieneś uczynić ją tak wyraźną, jak to tylko możliwe (a ponadto masz nad nią większą kontrolę), na przykład używając TryParse
metody.
Edycja: jaka jest różnica między nimi?
Zgodnie ze zaktualizowanym pytaniem i zachowaniem tego, co napisałem wcześniej (o tym, kiedy można użyć rzutowania w porównaniu do tego, kiedy można / trzeba go użyć Convert
), ostatni punkt do wyjaśnienia to czy są między nimi różnice (ponadto Convert
używa IConvertible
i IFormattable
interfejsów, aby mógł wykonywać operacje niedozwolone przy odlewach).
Krótka odpowiedź brzmi: tak, zachowują się inaczej . Postrzegam tę Convert
klasę jako klasę metod pomocniczych, więc często zapewnia pewne korzyści lub nieco inne zachowanie. Na przykład:
double real = 1.6;
int castedInteger = (int)real;
int convertedInteger = Convert.ToInt32(real);
Całkiem inny, prawda? Rzutowanie jest obcięte (wszyscy tego oczekujemy), ale Convert
wykonuje zaokrąglenie do najbliższej liczby całkowitej (a tego można się nie spodziewać, jeśli nie jesteś tego świadomy). Każda metoda konwersji wprowadza różnice, więc nie można zastosować ogólnej reguły i należy je rozpatrywać w każdym przypadku ... 19 typów podstawowych do konwersji na każdy inny typ ... lista może być dość długa, o wiele lepiej jest sprawdzić wielkość liter w MSDN walizka!