Mam funkcję, która rzuca a double
na string
wartości.
string variable = "5.00";
double varDouble = (double)variable;
Wprowadzono zmianę kodu i projekt jest kompilowany z błędem: System.InvalidCastException: Specified cast is not valid.
Jednak po wykonaniu następujących czynności ...
string variable = "5.00";
double varDouble = Convert.ToDouble(variable);
... projekt buduje się bez żadnych błędów.
Jaka jest różnica między rzutowaniem a zastosowaniem Convert.To()
metody? Dlaczego rzucanie rzuca, Exception
a używanie Convert.To()
nie?
Odpowiedzi:
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:
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
:// won't compile! 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
implementujeIFormattable
i toNotSupportedException
jest (pochodzi z),Exception
więc rzutowanie jest niejawne. Żadne informacje nie są tracone, ponieważ obiekty nie zmieniają swoich typów (jest inaczej w przypadkustruct
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:
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:
// won't compile! 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):
// won't compile! 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ą zConvert
implementacji iIConvertible
implementacji, ponieważ w C # z rzutowaniem mówisz do kompilatora:-lub-
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 formatstring
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ącdouble
: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 kiedyTryParse
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, implementacjiIConvertible
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; // Equals to Nullable<float> maybe = 10; float sure1 = (float)maybe; // With cast float sure2 = maybe.Value; // Without cast
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"; // Implicit from string double value = (string)text; // Explicit to double
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:
object
do dowolnego innego typu (może to obejmować również rozpakowywanie).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
:IConvertible
na dowolny inny (obsługiwany) typ.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
dodouble
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ącTryParse
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 (ponadtoConvert
używaIConvertible
iIFormattable
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; // 1 int convertedInteger = Convert.ToInt32(real); // 2
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!źródło
Difference between casting and using the Convert.To() method
. W przeciwnym razie bardzo wyczerpująca odpowiedź. (Mam nadzieję, że moje pytanie zostanie ponownie otwarte ...)double
wartości, które nie reprezentują liczb całkowitych, powinny być „zamienialne” naint
. Rzutowanie wydawałoby się odpowiednim paradygmatem w przypadkach, gdy np. Ktoś pobieraInt32
wartości z a,double[]
które przechowuje mieszankę liczb rzeczywistych iInt32
wartości, które zostały przekonwertowane nadouble
[próba konwersji wartości, której nie można dokładnie przedstawić w,int32
oznaczałaby nieoczekiwany stan i powinien wywołać wyjątek], ale myślę, że kiedy chce się konwersji stratnej, należy dokładnie określić żądaną formę.object o = 123; var l = Convert.ToInt64(o); var i = (long) (int) o; var f = (long) o // InvalidCastException
float
->int
), ale przymus . Może to być na przykładDerivedClass
->BaseClass
. Jest to mylące, ponieważ w C # używamy tego samego słowa (i operatora) dla obu, ale w rzeczywistości są to różne rzeczy. Formalna definicja rozróżnienia między nimi jest nieco bardziej skomplikowana niż to, co napisałem.Rzutowanie jest sposobem na powiedzenie kompilatorowi: „Wiem, że myślisz, że ta zmienna to Bar, ale ja wiem więcej niż ty; obiekt to w rzeczywistości Foo, więc potraktuję go tak, jakby to było Foo z teraz. ” Następnie, w czasie wykonywania, jeśli rzeczywisty obiekt okazał się naprawdę Foo, wtedy twój kod działa, jeśli okaże się, że obiekt w ogóle nie był Foo, wtedy pojawi się wyjątek. (W szczególności an
System.InvalidCastException
.)Z drugiej strony konwersja jest sposobem na powiedzenie: „Jeśli dasz mi obiekt typu Bar, mogę stworzyć zupełnie nowy obiekt Foo, który reprezentuje to, co jest w tym obiekcie Bar. Nie zmienię oryginalnego obiektu, wygrał” Jeśli traktuje oryginalny obiekt inaczej, utworzy coś nowego, opartego tylko na innej wartości . Jeśli chodzi o sposób, w jaki to zrobi, może to być wszystko. W takim przypadku
Convert.ToDouble
wywołanieDouble.Parse
który ma różnego rodzaju złożoną logikę do określania, jakie typy łańcuchów reprezentują wartości liczbowe. Mógłbyś napisać własną metodę konwersji, która odwzorowuje łańcuchy na dublety w inny sposób (być może w celu obsługi jakiejś zupełnie innej konwencji wyświetlania liczb, takich jak cyfry rzymskie lub cokolwiek innego). Konwersja może zrobić wszystko, ale chodzi o to, że tak naprawdę nie prosisz kompilatora, aby zrobił cokolwiek za Ciebie; to ty piszesz kod, aby określić, jak utworzyć nowy obiekt, ponieważ kompilator bez Twojej pomocy nie może wiedzieć, jak odwzorować (na przykład) astring
nadouble
.Więc kiedy się nawracasz, a kiedy rzucasz? W obu przypadkach mamy jakąś zmienną typu, powiedzmy A, i chcemy mieć zmienną typu B. Jeśli nasz obiekt A naprawdę, w rzeczywistości, pod maską, jest B, to rzucamy. Jeśli tak naprawdę nie jest to B, musimy go przekonwertować i zdefiniować, w jaki sposób program ma uzyskać B z A.
źródło
foreach
). Poza tymi wyjątkami rzuty są z definicji jawne.Od
MSDN
:Rozważmy następujący przykład:
double a = 2548.3; int b; b = (int)a; //2548 --> information (.3) lost in the conversion
I również:
Możesz użyć
System.Convert
class, gdy chcesz konwertować między niekompatybilnymi typami. Główną różnicą między odlewania i nawróconego jest kompilacji i czasu wykonywania . Wyjątki konwersji typów pojawiają się w czasie wykonywania , tj. Rzutowanie typu, które nie powiedzie się w czasie wykonywania, spowoduje zgłoszenieInvalidCastException
.Wniosek: podczas rzutowania mówisz kompilatorowi, który
a
jest naprawdę typem,b
a jeśli tak, projekt kompiluje się bez żadnych błędów, takich jak ten przykład:double s = 2; int a = (int) s;
Ale w konwersji mówisz do kompilatora istnieje sposób, aby utworzyć nowy obiekt z
a
typub
, zrób to i projekt buduje bez żadnych błędów, ale jak powiedziałem , jeśli typ nie oddanych w czasie wykonywania, spowoduje toInvalidCastException
, aby być rzuconym .Na przykład poniższy kod nigdy nie jest kompilowany, ponieważ kompilator wykrył, że nie może rzutować wyrażenia typu
DateTime
na typint
:DateTime s = DateTime.Now; int a = (int)(s);
Ale ten został pomyślnie skompilowany:
DateTime s = DateTime.Now; int a = Convert.ToInt32(s);
Ale w czasie wykonywania otrzymasz komunikat
InvalidCastException
:źródło
W
Convert.Double
rzeczywistości metoda wywołuje ją tylko wewnętrznieDouble.Parse(string)
.Ani
String
typ, ani typ nieDouble
definiują jawnej / niejawnej konwersji między dwoma typami, więc rzutowanie zawsze kończy się niepowodzeniem.Double.Parse
Metoda będzie wyglądać w każdej postaci wstring
i budować wartość numeryczną opartą na wartościach bohaterów wstring
. Jeśli którykolwiek ze znaków jest nieprawidłowy,Parse
metoda kończy się niepowodzeniem (powodując równieżConvert.Double
niepowodzenie metody).źródło
Convert.ToDouble()
spojrzałby poza bajty i rozważył dane?W Twoim przykładzie próbujesz rzutować ciąg na podwójny (typ niecałkowity).
Aby to zadziałało, wymagana jest wyraźna konwersja.
I muszę zaznaczyć, że mogłeś użyć
Convert.ToDouble
zamiast,Convert.ToInt64
ponieważ możesz stracić ułamkowe części podwójnej wartości podczas konwersji na int.jeśli zmienna ma wartość „5,25” varDouble wyniósłby 5,00 (strata 0,25 z powodu konwersji na Int64)
Aby odpowiedzieć na Twoje pytanie dotyczące przesyłania i konwersji.
Twoja obsada (jawna obsada) nie spełnia wymagań dla jawnej obsady. wartość, którą próbujesz rzutować za pomocą operatora rzutowania, jest nieprawidłowa (tj. niecałkowita).
Odwiedź tę stronę MSDN, aby zapoznać się z zasadami przesyłania / konwersji
źródło
Rzutowanie nie obejmuje żadnej konwersji, tj. Wewnętrzna reprezentacja wartości nie ulega zmianie. Przykład:
object o = "Hello"; // o is typed as object and contains a string. string s = (string)o; // This works only if o really contains a string or null.
Możesz przekonwertować a
double
nastring
takidouble d = 5; string s = d.ToString(); // -> "5" // Or by specifying a format string formatted = d.ToString("N2"); // -> "5.00"
Możesz przekonwertować a
string
na adouble
na kilka sposobów (tutaj tylko dwa z nich):string s = "5"; double d = Double.Parse(s); // Throws an exception if s does not contain a valid number
Albo w bezpieczny sposób
string s = "5"; double d; if (Double.TryParse(s, out d)) { Console.WriteLine("OK. Result = {0}", d); } else { Console.WriteLine("oops!"); }
źródło
Convert.ToDouble()
połączenia wewnętrzneDouble.Parse()
. Czy korzystanie z opcji „Convert.ToDouble()
over”Double.Parse()
lub „ over” jest korzystne i dlaczego?Convert.ToDouble
ma wiele przeciążeń, które akceptują różne typy danych wejściowych. Akceptowanie przeciążeniastring
zwraca,0.0
jeślinull
przekazano ciąg. Poza tym nie widzę żadnej korzyści w używaniu go.Double.Parse()
ma coś do zaoferowania, co powinienem rozważyć?Double.Parse()
jest bardziej bezpośredni niżConvert.ToDouble()
. Jeśli jesteś pewien, że Twój ciąg będzie zawierał prawidłową liczbę, możesz go bezpiecznie użyć, w przeciwnym razie radzę użyćDouble.TryParse
.string variable = "5.00"; double varDouble = (double)variable;
Powyższa konwersja jest po prostu niedozwolona przez język. Oto lista wyraźnych rzutów dla typów liczbowych: http://msdn.microsoft.com/en-us/library/yht2cx7b.aspx Jak widać, nawet nie każdy typ liczbowy można przekonwertować na inny typ liczbowy
Więcej informacji o przesyłaniu tutaj
Podczas rzutowania typu struktura danych nie ulega zmianie. Cóż, w przypadku konwersji wartości liczbowych można stracić kilka bitów lub uzyskać dodatkowe 0 bitów. Ale nadal pracujesz z liczbą.Po prostu zmieniasz ilość pamięci zajmowanej przez tę liczbę. Jest to wystarczająco bezpieczne, aby kompilator zrobił wszystko, co konieczne.
Ale kiedy próbujesz rzucić łańcuch na liczbę, nie możesz tego zrobić, ponieważ nie wystarczy zmienić ilości pamięci zajmowanej przez zmienną. Na przykład, 5.00jako łańcuch jest sekwencją „liczb”: 53 (5) 46 (.) 48 (0) 48 (0) - to jest dla ASCII, ale łańcuch będzie zawierał coś podobnego. Jeśli kompilator po prostu pobierze pierwsze N (4 dla podwójnego? Nie jestem pewien) z łańcucha - ten fragment będzie zawierał zupełnie inną podwójną liczbę. W tym samym czasie Convert.ToDouble () uruchamia specjalny algorytm, który pobierze każdy symbol łańcucha, obliczy cyfrę, którą reprezentuje i utworzy podwójną liczbę, jeśli łańcuch reprezentuje liczbę. Języki takie jak PHP, z grubsza mówiąc, będą wywoływać dla Ciebie Convert.ToDouble w tle. Ale C #, podobnie jak język statyczny, nie zrobi tego za Ciebie. Dzięki temu możesz mieć pewność, że każda operacja jest bezpieczna dla typu i nie otrzymasz czegoś nieoczekiwanego, wykonując coś takiego jak:
double d = (double)"zzzz"
źródło
Rzutowanie ciągu na taki podwójny jest niedozwolone C # i dlatego otrzymujesz wyjątek, musisz przekonwertować ciąg ( dokument MSDN, który pokazuje dopuszczalne ścieżki konwersji). Dzieje się tak po prostu dlatego, że łańcuch niekoniecznie będzie zawierał dane liczbowe, ale różne typy liczbowe będą (z wyjątkiem wartości null). A
Convert
uruchomi metodę, która sprawdzi łańcuch, aby zobaczyć, czy można go przekształcić w wartość liczbową. Jeśli tak, zwróci tę wartość. Jeśli nie, zgłosi wyjątek.Aby go przekonwertować, masz kilka opcji. Użyłeś
Convert
metodę w swoim pytaniu, nie maParse
co jest w dużej mierze podobny doConvert
, ale należy również spojrzeć na TryParse który pozwoli Ci zrobić:string variable = "5.00"; double varDouble; if (Double.TryParse(variable, out varDouble)) { //Code that runs if the conversion succeeded. } else { //Code that runs if the conversion failed. }
Pozwala to uniknąć możliwego wyjątku, jeśli spróbujesz
Convert
lubParse
ciągu nieliczbowego.źródło
TryParse
over,Convert
ponieważTryParse
sprawdza, czy konwersja się powiodła?double varDouble = (double)variable
zakłada, żevariable
jest to już podwójne. Jeślivariable
nie jest podwójnym (jest to ciąg), to się nie powiedzie.double varDouble = Convert.ToDouble(variable)
lubi, jak mówi - konwertuje.Jeśli może przeanalizować lub w inny sposób wyodrębnić podwójną wartość,variable
to zrobi.Po drugie używam
Double.Parse
lubDouble.TryParse
dlatego, że wyraźniej wskazuje, co ma się dziać. Zaczynasz od łańcucha i oczekujesz, że będzie można go zamienić na podwójny. Jeśli masz jakiekolwiek wątpliwości, użyjTryParse
.Jeśli
variable
jest argumentem metody, zmień typ na double. Spraw, aby dzwoniący był odpowiedzialny za podanie prawidłowego typu. W ten sposób kompilator wykona pracę za Ciebie.źródło
Najważniejsza różnica polega na tym, że jeśli używane jest rzutowanie typów i konwersja nie powiedzie się (powiedzmy, że konwertujemy bardzo dużą wartość zmiennoprzecinkową na wartość int), nie zostanie zgłoszony żaden wyjątek i zostanie wyświetlona minimalna wartość, jaką można przechowywać w wartości int. Ale w przypadku korzystania z Convert , wyjątek zostanie zgłoszony dla takich scenariuszy.
źródło