Czy warto używać „as” zamiast rzutowania, nawet jeśli nie ma kontroli zerowej? [Zamknięte]

351

Na blogach programistycznych, przykładach kodu online, a ostatnio nawet w książce, wciąż się potykam o kod:

var y = x as T;
y.SomeMethod();

lub, co gorsza:

(x as T).SomeMethod();

To nie ma dla mnie sensu. Jeżeli jesteś pewien, że xjest typu T, należy użyć bezpośredniego obsady: (T)x. Jeśli nie masz pewności, możesz użyć, asale musisz sprawdzić nullprzed wykonaniem jakiejś operacji. Wszystko, co robi powyższy kod, polega na przekształceniu (użytecznego) InvalidCastExceptionw (bezużyteczne) NullReferenceException.

Czy jestem jedynym, który uważa, że ​​to rażące nadużycie assłowa kluczowego? Czy może przeoczyłem coś oczywistego i powyższy wzór ma sens?

Heinzi
źródło
56
Byłoby zabawniej widzieć (pocałować jak S) .SteveIsSuchA (); Ale zgadzam się, to nadużycie.
SwDevMan81
5
To o wiele fajniejsze niż pisanie ((T)x).SomeMethod(), prawda? ;) (żartuję, masz rację oczywiście!)
Lucero
8
@P Tatusiu Nie zgadzam się, doskonale dobre pytanie (czy ten wzorzec kodu ma sens) i bardzo przydatne. +1 do pytania i marszczy brwi każdemu, kto głosuje na zamknięcie.
MarkJ
9
Lucerno ma rację, ten wzór kodowania jest indukowany przez próbę uniknięcia nawiasów. Nieuleczalne po narażeniu na Lisp.
Hans Passant
13
Zoptymalizowany kod (f as T).SomeMethod()
:;

Odpowiedzi:

252

Twoje zrozumienie jest prawdziwe. To brzmi jak próba mikrooptymalizacji. Powinieneś użyć normalnej obsady, gdy masz pewność co do typu. Oprócz generowania bardziej sensownego wyjątku, również szybko zawiedzie. Jeśli mylisz o swoim założeniu o rodzaju, program nie będzie natychmiast i będziesz mógł zobaczyć przyczynę awarii od razu, zamiast czekać na NullReferenceExceptionlub ArgumentNullExceptionlub nawet kiedyś logicznego błędu w przyszłości. Ogólnie rzecz biorąc, aswyrażenie, po którym nie następuje nullsprawdzenie, to zapach kodu.

Z drugiej strony, jeśli nie masz pewności co do rzutu, powinieneś użyć aszamiast normalnego rzutu owiniętego try-catchblokiem. Ponadto zaleca się użycie aszamiast kontroli typu, a następnie rzutowania. Zamiast:

if (x is SomeType)
   ((SomeType)x).SomeMethod();

który generuje isinstinstrukcję dla issłowa kluczowego i castclassinstrukcję dla obsady (skutecznie wykonując rzut dwukrotnie), powinieneś użyć:

var v = x as SomeType;
if (v != null)
    v.SomeMethod();

To tylko generuje isinstinstrukcję. Pierwsza metoda ma potencjalną wadę w aplikacjach wielowątkowych, ponieważ warunek wyścigu może spowodować, że zmienna zmieni swój typ po ispomyślnym sprawdzeniu i nie powiedzie się na linii rzutowania. Druga metoda nie jest podatna na ten błąd.


Poniższe rozwiązanie nie jest zalecane do użycia w kodzie produkcyjnym. Jeśli naprawdę nie znosisz tak fundamentalnej konstrukcji w języku C #, możesz rozważyć przejście na VB lub inny język.

Jeśli ktoś desperacko nienawidzi składni obsady, może napisać metodę rozszerzenia naśladującą obsadę:

public static T To<T>(this object o) { // Name it as you like: As, Cast, To, ...
    return (T)o;
}

i użyj ładnej składni [?]:

obj.To<SomeType>().SomeMethod()
Mehrdad Afshari
źródło
5
Myślę, że warunki wyścigu są nieistotne. Jeśli występuje ten problem, kod nie jest bezpieczny dla wątków i istnieją bardziej niezawodne sposoby jego rozwiązania niż użycie słowa kluczowego „as”. +1 za resztę odpowiedzi.
RMorrisey
10
@RMorrisey: Mam na myśli co najmniej jeden przykład: Załóżmy, że masz cacheobiekt, który inny wątek próbuje unieważnić, ustawiając go na null. W scenariuszach bez blokady takie rzeczy mogą się pojawić.
Mehrdad Afshari
10
is + cast wystarcza do wyzwolenia ostrzeżenia „Nie rzucaj niepotrzebnie” z FxCop: msdn.microsoft.com/en-us/library/ms182271.aspx To powinno wystarczyć do uniknięcia konstrukcji.
David Schmitt
2
Należy unikać używania metod rozszerzeń Object. Zastosowanie metody dla typu wartości spowoduje, że będzie niepotrzebnie zapakowane w ramkę.
MgSam
2
@MgSam Oczywiście taki przypadek użycia nie ma tu sensu To, ponieważ konwertuje on tylko w hierarchii dziedziczenia, która dla typów wartości i tak wymaga boksu. Oczywiście cały pomysł jest bardziej teoretyczny niż poważny.
Mehrdad Afshari
42

IMHO, aspo prostu sens w połączeniu z nullczekiem:

var y = x as T;
if (y != null)
    y.SomeMethod();
Rubens Farias
źródło
40

Użycie „as” nie stosuje konwersji zdefiniowanych przez użytkownika, a obsada użyje ich w stosownych przypadkach. W niektórych przypadkach może to być istotna różnica.

Larry Fix
źródło
5
Należy o tym pamiętać. Eric Lippert omawia
P Daddy
5
Niezły komentarz, P! Jeśli twój kod zależy od tego rozróżnienia, powiedziałbym, że w twojej przyszłości będzie sesja debugowania późną nocą.
TrueWill,
36

Tutaj napisałem o tym trochę:

http://blogs.msdn.com/ericlippert/archive/2009/10/08/what-s-the-difference-between-as-and-cast-operators.aspx

Rozumiem twój punkt widzenia. I zgadzam się z tym, co mówi: operator rzutowania komunikuje „Jestem pewien, że ten obiekt można przekonwertować na ten typ i jestem gotów zaryzykować wyjątek, jeśli się mylę”, podczas gdy operator „as” komunikuje się „Nie jestem pewien, czy ten obiekt można przekonwertować na ten typ; jeśli mam rację, daj mi wartość zerową”.

Istnieje jednak subtelna różnica. (x jako T). Cokolwiek () komunikuje „Wiem nie tylko, że x można przekonwertować na T, ale ponadto, że robi to tylko konwersje odniesienia lub rozpakowania, a ponadto, że x nie jest zerowy”. To komunikuje inne informacje niż ((T) x). Cokolwiek () i być może właśnie tego zamierza autor kodu.

Eric Lippert
źródło
1
Nie zgadzam się z twoją spekulacyjną obroną autora kodu w ostatnim zdaniu. przekazuje ((T)x).Whatever() również, że xnie jest [zamierzone] na zero, i bardzo wątpię, aby autor zazwyczaj dbał o to, czy konwersja Tnastąpi tylko z konwersjami referencyjnymi lub rozpakowanymi, czy też wymaga konwersji zdefiniowanej przez użytkownika lub zmieniającej reprezentację. W końcu, jeśli zdefiniuję public static explicit operator Foo(Bar b){}, to wyraźnie moją intencję Barnależy uznać za zgodną Foo. Rzadko chciałbym uniknąć tej konwersji.
P Daddy,
4
Cóż, być może większość autorów kodu nie wprowadziłaby tak subtelnego rozróżnienia. Mógłbym osobiście, ale gdybym tak był, dodałbym komentarz do tego efektu.
Eric Lippert,
16

Często widziałem odniesienia do tego wprowadzającego w błąd artykułu jako dowodu, że „as” jest szybsze niż casting.

Jednym z bardziej oczywistych wprowadzających w błąd aspektów tego artykułu jest grafika, która nie wskazuje na to, co jest mierzone: podejrzewam, że mierzy nieudane rzuty (gdzie „as” jest oczywiście znacznie szybsze, ponieważ nie jest zgłaszany żaden wyjątek).

Jeśli poświęcisz czas na wykonanie pomiarów, zobaczysz, że rzutowanie jest, jak można się spodziewać, szybsze niż „as”, gdy rzutowanie się powiedzie.

Podejrzewam, że może to być jeden z powodów użycia „kultowego ładunku” słowa kluczowego zamiast obsady.

Joe
źródło
2
Dzięki za link, to bardzo interesujące. Z tego, jak zrozumiałem artykuł, porównuje przypadek nie będący wyjątkiem. Niemniej jednak artykuł został napisany dla .net 1.1, a komentarze wskazują, że zmieniło się to w .net 2.0: Wydajność jest teraz prawie równa, a rzutowanie prefiksu jest nawet nieco szybsze.
Heinzi
1
Z tego artykułu wynika, że ​​porównuje przypadek nie będący wyjątkiem, ale już dawno przeprowadziłem kilka testów i nie byłem w stanie odtworzyć jego deklarowanych wyników, nawet z .NET 1.x. A ponieważ artykuł nie zawiera kodu używanego do uruchomienia testu porównawczego, nie można powiedzieć, co jest porównywane.
Joe
„Kult ładunku” - idealny. Sprawdź „Cargo Cult Science Richard Feynman”, aby uzyskać pełne informacje.
Bob Denny
11

Bezpośrednia obsada wymaga pary nawiasów więcej niż assłowa kluczowego. Więc nawet w przypadku, gdy masz 100% pewności, jaki jest typ, zmniejsza to bałagan wizualny.

Zgodziłem się jednak co do wyjątku. Ale przynajmniej dla mnie większość zastosowań assprowadza się do sprawdzenia nullpóźniej, co wydaje mi się lepsze niż wyłapanie wyjątku.

Joey
źródło
8

99% czasu, kiedy używam „as”, ma miejsce, gdy nie jestem pewien, jaki jest rzeczywisty typ obiektu

var x = obj as T;
if(x != null){
 //x was type T!
}

i nie chcę wychwytywać jawnych wyjątków rzutowania ani podwójnie rzucać, używając „is”:

//I don't like this
if(obj is T){
  var x = (T)obj; 
}
Max Galkin
źródło
8
Właśnie opisałeś właściwy przypadek użycia as. Jaki jest pozostały 1%?
P Daddy,
W literówce? =) Miałem na myśli 99% czasu, kiedy używam tego dokładnego fragmentu kodu, podczas gdy czasami mogę użyć „as” w wywołaniu metody lub w innym miejscu.
Max Galkin
D'oh, a jak to jest mniej przydatne niż druga popularna odpowiedź ???
Max Galkin
2
+1 Zgadzam się, że wywołanie tego jest tak samo cenne jak odpowiedź Rubensa Fariasa - ludzie, mam nadzieję, przyjadą tutaj i będzie to przydatny przykład
Ruben Bartelink
8

To dlatego, że ludziom podoba się to, jak wygląda, jest bardzo czytelny.

Spójrzmy prawdzie w oczy: operator rzutowania / konwersji w językach podobnych do C jest dość straszny, jeśli chodzi o czytelność. Wolałbym, żeby C # przyjął albo składnię JavaScript:

object o = 1;
int i = int(o);

Lub zdefiniuj tooperator, będący odpowiednikiem rzutowania as:

object o = 1;
int i = o to int;
JulianR
źródło
Dla pewności, wspomniana składnia JavaScript jest również dozwolona w C ++.
P Daddy,
@PDaddy: Nie jest to jednak bezpośrednia w 100% kompatybilna alternatywna składnia i nie jest tak przeznaczona (operator X kontra konstruktor konwersji)
Ruben Bartelink
Wolałbym używać składni C ++ dynamic_cast<>()(i podobnych). Robisz coś brzydkiego, powinno to wyglądać brzydko.
Tom Hawtin - tackline
5

Ludzie astak bardzo lubią , ponieważ dzięki temu czują się bezpieczni od wyjątków ... Jak gwarancja na pudełku. Facet kładzie na pudełku fantazyjną gwarancję, ponieważ chce, żebyś poczuł w sobie ciepło i ciepło. Myślisz, że włożyłeś w nocy to małe pudełko pod poduszkę, Wróżka Gwarancyjna może zejść i opuścić kwaterę, mam rację Ted?

Wracając do tematu ... gdy używasz rzutowania bezpośredniego, istnieje możliwość wystąpienia niepoprawnego wyjątku rzutowania. Tak więc ludzie stosują się asjako ogólne rozwiązanie wszystkich swoich potrzeb castingowych, ponieważ as(samo w sobie) nigdy nie zgłasza wyjątku. Ale zabawną rzeczą jest to, że w przykładzie, który podałeś (x as T).SomeMethod();, wymieniasz niepoprawny wyjątek rzutowania na wyjątek zerowy. Co zaciemnia prawdziwy problem, gdy widzisz wyjątek.

Na ogół nie używam aszbyt wiele. Wolę istest, ponieważ dla mnie wydaje się bardziej czytelny i ma więcej sensu niż próba obsady i sprawdzenie, czy nie ma nic.

Kok
źródło
2
„Wolę, że jest test” - „jest”, po czym rzutowanie jest oczywiście wolniejsze niż „jak”, po czym następuje test na wartość zerową (podobnie jak „IDictionary.ContainsKey”, a następnie dereferencje przy użyciu indeksu są wolniejsze niż „IDictionary.TryGetValue „). Ale jeśli uznasz to za bardziej czytelne, bez wątpienia różnica jest rzadko znacząca.
Joe
Ważnym stwierdzeniem w środkowej części jest to, jak ludzie stosują się asjako ogólne rozwiązanie, ponieważ zapewnia im poczucie bezpieczeństwa.
Bob
5

To musi być jeden z moich najlepszych pomysłów .

D & E Stroustrup i / lub jakiś post na blogu, którego nie mogę teraz znaleźć, omawia pojęcie tooperatora, który zająłby się kwestią przedstawioną przez https://stackoverflow.com/users/73070/johannes-rossel (tj. Taką samą składnią jak asz DirectCastsemantyką ).

Powodem, dla którego ta implementacja nie została wdrożona, jest to, że obsada powinna powodować ból i być brzydka, więc zostaniesz odepchnięty od korzystania z niej.

Szkoda, że ​​„sprytni” programiści (często autorzy książek (Juval Lowy IIRC)) omijają to, nadużywając asw ten sposób (C ++ nie oferuje as, prawdopodobnie z tego powodu).

Nawet VB ma większą spójność posiadające jednolitą składnię, że zmusza do wyboru TryCastalbo DirectCasti uzupełnić swój umysł !

Ruben Bartelink
źródło
+1. Prawdopodobnie miałeś na myśli DirectCast zachowanie , a nie składnię .
Heinzi
@Heinzi: Ta za +1. Słuszna uwaga. Zdecydowałem się być inteligentny i użyć semanticszamiast tego: P
Ruben Bartelink
Biorąc pod uwagę, że C # nie ma pretensji do zgodności z C, C ++ lub Javą, jestem zirytowany niektórymi rzeczami, które pożycza z tych języków. Wykracza poza „Wiem, że to X” i „Wiem, że to nie X, ale może być reprezentowane jako jeden”, do „Wiem, że to nie X i może nie być tak naprawdę reprezentowalny jako jeden , ale i tak daj mi X. ” Widziałem użyteczność dla double-to-intrzutowania, który zawiódłby, gdyby doublenie reprezentował dokładnej wartości, która mogłaby się zmieścić w Int32, ale uzyskanie (int)-1.5wydajności -1 jest po prostu brzydkie.
supercat
@supercat Tak, ale projektowanie języka nie jest łatwe, jak wszyscy wiemy - spójrz na zestaw kompromisów związanych z C # nullables. Jedynym znanym antidotum jest regularne czytanie C # w kolejnych edycjach Depth :) Na szczęście bardziej martwię się zrozumieniem niuansów F # w dzisiejszych czasach i jest o wiele bardziej rozsądny w wielu tych sprawach.
Ruben Bartelink
@RubenBartelink: Nie jestem do końca jasne, jakie dokładnie problemy powinny rozwiązać typy zerowalne, ale wydaje mi się, że w większości przypadków lepiej byłoby mieć MaybeValid<T>dwa publiczne pola IsValidi Valuektóry kod mógłby zrobić, jeśli uzna to za stosowne. To pozwoliłoby np MaybeValid<TValue> TryGetValue(TKey key) { var ret = default(MaybeValid<TValue>); ret.IsValid = dict.TryGetValue(key, out ret.Value); return ret; }. Pozwoliłoby to nie tylko zaoszczędzić co najmniej dwie operacje kopiowania w porównaniu z nimi Nullable<T>, ale może również być przydatne w przypadku każdego typu T- nie tylko klas.
supercat
2

Uważam, że to assłowo kluczowe może być uważane za bardziej elegancką wersję dynamic_castC ++.

Andrew Garrison
źródło
Powiedziałbym, że prosta obsada w C # jest bardziej podobna dynamic_castdo C ++.
P Daddy,
myślę, że rzutowanie w C # jest bardziej równoważne static_cast w C ++.
Andrew Garrison
2
@Ruben Bartelink: Zwraca tylko null ze wskaźnikami. Z referencjami, których powinieneś używać, gdy to możliwe, rzuca std::bad_cast.
P Daddy,
1
@Andrew Garrison: static_castnie wykonuje żadnej kontroli typu środowiska wykonawczego. Nie ma podobnej obsady do tego w języku C #.
P Daddy,
Niestety, nie wiedziałem, że możesz nawet używać rzutów w referencjach, ponieważ użyłem ich tylko w odniesieniu do wskaźników, ale P Daddy ma absolutną rację!
Andrew Garrison
1

Prawdopodobnie jest bardziej popularny bez powodu technicznego, ale dlatego, że jest łatwiejszy do odczytania i bardziej intuicyjny. (Nie mówię, że dzięki temu lepiej jest po prostu odpowiedzieć na pytanie)

Jla
źródło
1

Jednym z powodów używania „jako”:

T t = obj as T;
 //some other thread changes obj to another type...
if (t != null) action(t); //still works

Zamiast (zły kod):

if (obj is T)
{
     //bang, some other thread changes obj to another type...
     action((T)obj); //InvalidCastException
}
Rauhotz
źródło
2
Jeśli masz warunki wyścigowe, to masz większe problemy (ale zgódź się, że jest to dobra próbka do pójścia z innymi, więc +1
Ruben Bartelink
-1, ponieważ utrwala to błąd. Jeśli inne wątki mogą zmieniać typ obj, nadal masz problemy. Twierdzenie „// nadal działa” jest mało prawdopodobne, aby było prawdziwe, ponieważ t zostanie użyty jako wskaźnik do T, ale wskazuje na pamięć, która nie jest już T. Żadne rozwiązanie nie zadziała, gdy drugi wątek zmieni typ obj podczas działania (t).
Stephen C. Steel
5
@Stephen C. Steel: Wyglądasz na zdezorientowanego. Zmiana typu objoznaczałaby zmianę objsamej zmiennej, tak aby zawierała odwołanie do innego obiektu. Nie zmieniłoby to zawartości pamięci, w której znajduje się obiekt, do którego pierwotnie się odwołuje obj. Ten oryginalny obiekt pozostałby niezmieniony, a tzmienna nadal zawierałaby odniesienie do niego.
P Daddy,
1
@P Tato - myślę, że masz rację, a myliłem się: jeśli obiekt został odbity od obiektu T do obiektu T2, t nadal wskazywałoby na stary obiekt T. Ponieważ t nadal odwołuje się do starego obiektu, nie można go wyrzucać, więc stary obiekt T pozostanie ważny. Moje obwody detekcji warunków wyścigowych zostały przeszkolone na C ++, gdzie potencjalny problem stanowiłby podobny kod wykorzystujący dynamiczny przekaz.
Stephen C. Steel