Bezpośrednie rzutowanie vs operator „jak”?

709

Rozważ następujący kod:

void Handler(object o, EventArgs e)
{
   // I swear o is a string
   string s = (string)o; // 1
   //-OR-
   string s = o as string; // 2
   // -OR-
   string s = o.ToString(); // 3
}

Jaka jest różnica między tymi trzema rodzajami rzutów (okej, trzeci nie jest rzutem, ale masz zamiar). Który powinien być preferowany?

nullDev
źródło
1
Niezupełnie duplikat, ale w poprzednim pytaniu są również dyskusje na temat wydajności .
Nieskompletowane
8
4th string s = Convert.ToString(o):; 5: string s = $"{o}"(lub równoważnie string.Formatformularz dla wcześniejszego C #)
Earth Engine

Odpowiedzi:

833
string s = (string)o; // 1

Zgłasza wyjątek InvalidCastException, jeśli onie jest to string. W przeciwnym razie przypisuje odo s, nawet jeśli ojest null.

string s = o as string; // 2

Przypisuje nulldo sjeśli onie to stringczy ojest null. Z tego powodu nie można go używać z typami wartości ( nullw takim przypadku operator nigdy nie mógł wrócić ). W przeciwnym razie przypisuje odo s.

string s = o.ToString(); // 3

Powoduje wyjątek NullReferenceException, jeśli ojest null. Przypisuje wszystko, o.ToString()do czego wraca s, bez względu na typ o.


Użyj 1 do większości konwersji - to proste i jednoznaczne. Zwykle prawie nigdy nie używam 2, ponieważ jeśli coś nie jest odpowiedniego typu, zwykle spodziewam się wyjątku. Widziałem tylko potrzebę tego typu funkcji return-null ze źle zaprojektowanymi bibliotekami, które używają kodów błędów (np. Return null = error, zamiast wyjątków).

3 nie jest rzutowaniem i jest tylko wywołaniem metody. Użyj go, gdy potrzebujesz reprezentacji ciągu obiektu nie będącego ciągiem.

Szlifierka
źródło
2
Możesz przypisać „null” do typów wartości, jeśli są wyraźnie zdefiniowane, np .: int? ja; ciąg s = „5”; i = s jako int; // i ma teraz 5 s = null; i = s jako int; // i jest teraz pusty
Anheledir
3
RE: Anheledir Właściwie byłbym zerowy po pierwszym telefonie. Musisz użyć jawnej funkcji konwersji, aby uzyskać wartość ciągu.
Guvante,
45
RE: Sander Właściwie istnieje jeszcze jeden bardzo dobry powód, aby go użyć, ponieważ upraszcza kod sprawdzający (Sprawdź, czy null, a następnie sprawdź, czy null i poprawny typ) Jest to pomocne, ponieważ często wolisz rzucić niestandardowy wyjątek. Ale prawdą jest, że ślepe, ponieważ połączenia są złe.
Guvante,
5
# 2 jest przydatny w przypadku takich metod, jak metody Equals, w których nie znasz typu danych wejściowych. Ogólnie jednak tak, 1 byłby preferowany. Chociaż wolałbym od tego, to oczywiście użycie systemu typów do ograniczenia do jednego typu, gdy oczekujesz tylko jednego :)
Calum
6
# 2 jest również przydatny, gdy masz kod, który może zrobić coś specyficznego dla wyspecjalizowanego typu, ale w przeciwnym razie nic by nie zrobił.
AnthonyWJones
349
  1. string s = (string)o;Użyj, gdy coś zdecydowanie powinno być drugą rzeczą.
  2. string s = o as string;Użyj, gdy coś może być inną rzeczą.
  3. string s = o.ToString(); Użyj, gdy nie obchodzi Cię, co to jest, ale po prostu chcesz użyć dostępnej reprezentacji ciągu.
Quibblesome
źródło
1
Wydaje mi się, że ta odpowiedź brzmi dobrze, ale może nie być dokładna.
j riv
1
Lubię dwa pierwsze, ale dodam „i jesteś pewien, że to nie jest zero” do trzeciej opcji.
Uxonith,
2
możesz teraz używać Elvisa (?.), aby uniknąć konieczności dbania o to: obj? .ToString ()
Quibblesome
@Quibblesome - świetna odpowiedź, ale musiałem przestać myśleć o twoim obaleniu! dosłownie budzi mi się w głowie, że język istnieje już od ponad 15 lat. To wydaje się wczoraj, kiedy wszyscy byliśmy „nerwowi”, próbując przekonać starszych deweloperów do przejścia na C #.
Griswald_911,
1
@Quibblesome ładna odpowiedź: czy denerwujesz się, jeśli dodam, jakie są 1/2/3, aby nie było potrzeby przewijania w górę do OP. Ja z SO oceniłbym stare odpowiedzi według głosów!
whytheq
29

To naprawdę zależy od tego, czy wiesz, czy ojest to łańcuch, i co chcesz z nim zrobić. Jeśli twój komentarz oznacza, że otak naprawdę jest to struna, wolałbym (string)orzut prosty - raczej nie zawiedzie.

Największą zaletą używania prostego rzutowania jest to, że gdy się nie powiedzie, otrzymujesz InvalidCastException , który mówi ci prawie, co poszło nie tak.

Z asoperatorem, jeśli onie jest łańcuchem, sjest ustawiony na null, co jest przydatne, jeśli nie masz pewności i chcesz przetestować s:

string s = o as string;
if ( s == null )
{
    // well that's not good!
    gotoPlanB();
}

Jeśli jednak nie wykonasz tego testu, użyjesz go spóźniej i zostanie zgłoszony wyjątek NullReferenceException . Te wydają się być bardziej powszechne i wiele trudniej wytropić gdy tylko dzieje się w środowisku naturalnym, jak prawie każda linia dereferences zmienną i może rzucić jeden. Z drugiej strony, jeśli próbujesz rzutować na typ wartości (dowolny prymityw lub struktury takie jak DateTime ), musisz użyć prostego rzutowania - asnie zadziała.

W szczególnym przypadku konwersji na ciąg, każdy obiekt ma ToString, więc trzecia metoda może być w porządku, jeśli onie jest zerowa i uważasz, że ToStringmetoda może zrobić to, co chcesz.

Blair Conrad
źródło
2
Jedna uwaga - możesz używać asz zerowymi typami wartości. IE o as DateTimenie będzie działać, ale o as DateTime?będzie ...
John Gibb
Dlaczego nie użyć if (s is string)zamiast tego?
BornToCode,
1
@BornToCode, dla mnie, w dużej mierze osobiste preferencje. W zależności od tego, co robisz, często po ising, będziesz musiał ponownie rzucić, więc masz mocną, a potem ciężką obsadę. Z jakiegoś powodu askontrola zerowa wydawała mi się lepsza.
Blair Conrad
9

Jeśli już wiesz, na jaki typ może on rzutować, użyj rzutowania w stylu C:

var o = (string) iKnowThisIsAString; 

Zauważ, że tylko z rzutowaniem w stylu C można wykonać jawny przymus typu.

Jeśli nie wiesz, czy jest to pożądany typ i zamierzasz go użyć, jeśli tak, użyj jako słowa kluczowego:

var s = o as string;
if (s != null) return s.Replace("_","-");

//or for early return:
if (s==null) return;

Należy zauważyć, że jak nie będzie wywoływać żadnych operatorów konwersji typu. Będzie ona różna od null tylko wtedy, gdy obiekt nie ma wartości null i natywnie określonego typu.

Użyj ToString (), aby uzyskać czytelny dla człowieka ciąg znaków reprezentujący dowolny obiekt, nawet jeśli nie można rzutować na ciąg.

Mark Cidade
źródło
3
To interesująca mała gotcha dotycząca operatorów konwersji typu. Mam kilka typów, dla których utworzyłem konwersje, dlatego muszę na to uważać.
AnthonyWJones
7

Słowo kluczowe as jest dobre w asp.net, gdy używasz metody FindControl.

Hyperlink link = this.FindControl("linkid") as Hyperlink;
if (link != null)
{
     ...
}

Oznacza to, że możesz operować na zmiennej tekstowej, a nie rzutować ją objecttak, jak w przypadku rzutowania bezpośredniego:

object linkObj = this.FindControl("linkid");
if (link != null)
{
     Hyperlink link = (Hyperlink)linkObj;
}

Nie jest to wielka sprawa, ale oszczędza wiersze kodu i przypisywanie zmiennych, a ponadto jest bardziej czytelna

Glenn Slaven
źródło
6

„as” opiera się na „is”, które jest słowem kluczowym, które sprawdza w czasie wykonywania, czy obiekt jest polimorficznie kompatybilny (w zasadzie jeśli można wykonać rzutowanie) i zwraca wartość null, jeśli sprawdzenie się nie powiedzie.

Te dwa są równoważne:

Używanie „jako”:

string s = o as string;

Używanie „is”:

if(o is string) 
    s = o;
else
    s = null;

Przeciwnie, rzutowanie w stylu c jest wykonywane również w czasie wykonywania, ale zgłasza wyjątek, jeśli rzutowanie nie może być wykonane.

Aby dodać ważny fakt:

Słowo kluczowe „as” działa tylko z typami referencji. Nie możesz zrobić:

// I swear i is an int
int number = i as int;

W takich przypadkach musisz użyć rzutowania.

Sergio Acosta
źródło
Dzięki za wskazanie mojego błędu, masz rację. Zredagowałem odpowiedź. UPS przepraszam.
Sergio Acosta
5

2 jest przydatny do rzutowania na typ pochodny.

Załóżmy, że a jest zwierzęciem:

b = a as Badger;
c = a as Cow;

if (b != null)
   b.EatSnails();
else if (c != null)
   c.EatGrass();

dostanie karmione minimum odlewów.

Joel in Gö
źródło
2
@Chirs Moutray, nie zawsze jest to możliwe, szczególnie jeśli jest to biblioteka.
spowolnił określony w
5

Według eksperymentów przeprowadzonych na tej stronie: http://www.dotnetguru2.org/sebastienros/index.php/2006/02/24/cast_vs_as

(czasami na tej stronie pojawiają się błędy „niedozwolonego odsyłacza”, więc odśwież ją, jeśli tak się dzieje)

Wniosek jest taki, że operator „jak” jest zwykle szybszy niż rzut. Czasem wiele razy szybszy, czasem ledwo szybszy.

Uważam peronicznie, że „jak” jest również bardziej czytelne.

Tak więc, ponieważ jest on zarówno szybszy, jak i „bezpieczniejszy” (nie rzuca wyjątku) i być może łatwiejszy do odczytania, zalecam używanie „jako” przez cały czas.

Brady Moritz
źródło
4

„(string) o” ​​spowoduje wyjątek InvalidCastException, ponieważ nie ma bezpośredniego przesyłania.

„o jako ciąg” spowoduje, że s będzie odwołaniem zerowym, a nie zgłoszonym wyjątkiem.

„o.ToString ()” nie jest obsadą żadnego rodzaju per se, jest to metoda implementowana przez obiekt, a więc w taki czy inny sposób, przez każdą klasę w .net, która „robi coś” z instancją wywołana klasa i zwraca ciąg znaków.

Nie zapominaj, że do konwersji na ciąg znaków istnieje również Convert.ToString (someType instanceOfThatType), gdzie someType jest jednym z zestawu typów, głównie typów bazowych frameworka.

Obrabować
źródło
3

Wszystkie podane odpowiedzi są dobre, jeśli mogę coś dodać: Aby bezpośrednio użyć metod i właściwości ciągu (np. ToLower), nie możesz napisać:

(string)o.ToLower(); // won't compile

możesz pisać tylko:

((string)o).ToLower();

ale możesz zamiast tego napisać:

(o as string).ToLower();

Ta asopcja jest bardziej czytelna (przynajmniej moim zdaniem).

BornToCode
źródło
Konstrukcja (o jako ciąg) .ToLower () pokonuje cel operatora as. Spowoduje to zgłoszenie wyjątku zerowego odniesienia, gdy nie można rzutować o na string.
James
@james - Ale kto powiedział, że jedynym celem operatora as jest zgłoszenie wyjątku, jeśli rzutowanie się nie powiedzie? Jeśli wiesz, że o jest łańcuchem i po prostu chcesz napisać czystszy kod, możesz użyć (o as string).ToLower()zamiast wielu mylących nawiasów.
BornToCode,
cel as jest wręcz przeciwny - nie powinien wyrzucać wyjątku, gdy rzutowanie się nie powiedzie, powinien zwrócić wartość null. Powiedzmy, że twoje o jest łańcuchem o wartości null, co się wtedy stanie? Wskazówka - połączenie ToLower zakończy się niepowodzeniem.
James
@james - Masz rację, ale co z przypadkami, w których wiem na pewno, że nie będzie to wartość zerowa i po prostu muszę wykonać rzutowanie dla kompilatora, aby umożliwić mi dostęp do metod tego obiektu?
BornToCode,
1
zdecydowanie możesz to zrobić, ale nie jest to najlepsza praktyka, ponieważ nie chcesz polegać na dzwoniącym lub systemach zewnętrznych, aby upewnić się, że twoja wartość nie jest zerowa. Jeśli używasz C # 6, możesz zrobić (o jako ciąg) ?. Obniżyć().
James
3
string s = o as string; // 2

Jest preferowany, ponieważ pozwala uniknąć kary za wydajność podwójnego rzucania.

Chris S.
źródło
Cześć Chris, link, który był w tej odpowiedzi, to teraz 404 ... Nie jestem pewien, czy masz zamiennik, który chcesz umieścić na swoim miejscu?
Matt
3

Wygląda na to, że obie są różne pod względem koncepcyjnym.

Direct Casting

Typy nie muszą być ściśle powiązane. Występuje we wszystkich rodzajach smaków.

  • Niestandardowe rzutowanie niejawne / jawne: zwykle tworzony jest nowy obiekt.
  • Typ wartości Ujawniona: Kopiuj bez utraty informacji.
  • Typ wartości Jawne: Kopiowanie i informacje mogą zostać utracone.
  • Relacja IS-A: Zmień typ odniesienia, w przeciwnym razie zgłasza wyjątek.
  • Ten sam typ: „Przesyłanie jest zbędne”.

Wydaje się, że obiekt zostanie przekształcony w coś innego.

Operator AS

Rodzaje mają bezpośredni związek. Jak w:

  • Typy referencyjne: relacja IS-A Obiekty są zawsze takie same, zmieniają się tylko referencje.
  • Typy wartości: Kopiuj typy boksu i zerowania.

Wydaje się, że będziesz obsługiwać obiekt w inny sposób.

Próbki i IL

    class TypeA
    {
        public int value;
    }

    class TypeB
    {
        public int number;

        public static explicit operator TypeB(TypeA v)
        {
            return new TypeB() { number = v.value };
        }
    }

    class TypeC : TypeB { }
    interface IFoo { }
    class TypeD : TypeA, IFoo { }

    void Run()
    {
        TypeA customTypeA = new TypeD() { value = 10 };
        long longValue = long.MaxValue;
        int intValue = int.MaxValue;

        // Casting 
        TypeB typeB = (TypeB)customTypeA; // custom explicit casting -- IL:  call class ConsoleApp1.Program/TypeB ConsoleApp1.Program/TypeB::op_Explicit(class ConsoleApp1.Program/TypeA)
        IFoo foo = (IFoo)customTypeA; // is-a reference -- IL: castclass  ConsoleApp1.Program/IFoo

        int loseValue = (int)longValue; // explicit -- IL: conv.i4
        long dontLose = intValue; // implict -- IL: conv.i8

        // AS 
        int? wraps = intValue as int?; // nullable wrapper -- IL:  call instance void valuetype [System.Runtime]System.Nullable`1<int32>::.ctor(!0)
        object o1 = intValue as object; // box -- IL: box [System.Runtime]System.Int32
        TypeD d1 = customTypeA as TypeD; // reference conversion -- IL: isinst ConsoleApp1.Program/TypeD
        IFoo f1 = customTypeA as IFoo; // reference conversion -- IL: isinst ConsoleApp1.Program/IFoo

        //TypeC d = customTypeA as TypeC; // wouldn't compile
    }
Lucas Teixeira
źródło
2

Chciałbym zwrócić uwagę na następujące cechy operatora as :

https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/ke words/as

Zauważ, że operator as wykonuje tylko konwersje referencyjne, konwersje zerowalne i konwersje bokserskie. Operator as nie może wykonywać innych konwersji, takich jak konwersje zdefiniowane przez użytkownika, które zamiast tego powinny być wykonywane przy użyciu wyrażeń rzutowanych.

Vadim S.
źródło
0

Gdy próbuję uzyskać ciąg reprezentujący dowolny element (dowolnego typu), który potencjalnie może mieć wartość NULL, wolę poniższy wiersz kodu. Jest kompaktowy, wywołuje ToString () i poprawnie obsługuje wartości zerowe. Jeśli o ma wartość null, s będzie zawierać String.Empty.

String s = String.Concat(o);
xtrem
źródło
0

Ponieważ nikt o tym nie wspominał, słowo kluczowe najbliższe instanceOf Java jest następujące:

obj.GetType().IsInstanceOfType(otherObj)
Bennett Yeo
źródło
0

Użyj rzutowania bezpośredniego, string s = (string) o;jeśli w logicznym kontekście Twojej aplikacji stringjest to jedyny prawidłowy typ. Dzięki takiemu podejściu uzyskasz InvalidCastExceptioni wdrożysz zasadę niezawodności . Twoja logika będzie chroniona przed dalszym przekazywaniem nieprawidłowego typu lub otrzymaniem NullReferenceException, jeśli zostanie użyty asoperator.

Jeśli logika oczekuje użycia kilku różnych typów string s = o as string;i sprawdź ją nulllub użyj isoperatora.

Nowa wersja fajna pojawiła się w C # 7.0, aby uprościć rzutowanie, a sprawdzenie to dopasowanie wzoru :

if(o is string s)
{
  // Use string variable s
}

or

switch (o)
{
  case int i:
     // Use int variable i
     break;
  case string s:
     // Use string variable s
     break;
 }
Dmitry
źródło
0

W C # obsługiwane są następujące dwie formy konwersji typu (rzutowanie):

|

(C) przeciwko

• Konwertuj typ statyczny v na c w podanym wyrażeniu

• Możliwe tylko wtedy, gdy typem dynamicznym v jest c lub podtyp c

• Jeśli nie, zgłaszany jest wyjątek InvalidCastException

|

v jako C

• Nieśmiertelny wariant (c) v

• W ten sposób przekonwertuj typ statyczny v na c w danym wyrażeniu

• Zwraca null, jeśli typem dynamicznym v nie jest c lub podtyp c


źródło