Podczas programowania interfejsów odkryłem, że dużo rzucam lub przekształcam typy obiektów.
Czy istnieje różnica między tymi dwiema metodami konwersji? Jeśli tak, to czy istnieje różnica kosztów lub jak to wpływa na mój program?
public interface IMyInterface
{
void AMethod();
}
public class MyClass : IMyInterface
{
public void AMethod()
{
//Do work
}
// Other helper methods....
}
public class Implementation
{
IMyInterface _MyObj;
MyClass _myCls1;
MyClass _myCls2;
public Implementation()
{
_MyObj = new MyClass();
// What is the difference here:
_myCls1 = (MyClass)_MyObj;
_myCls2 = (_MyObj as MyClass);
}
}
Ponadto, jaka jest „ogólnie” preferowana metoda?
Odpowiedzi:
Odpowiedź poniżej linii została napisana w 2008 roku.
W C # 7 wprowadzono dopasowanie wzorca, które w dużej mierze zastąpiło
as
operatora, ponieważ można teraz pisać:Zauważ, że
tt
po tym jest nadal w zasięgu, ale nie jest zdecydowanie przypisany. (Jest to zdecydowanie przypisane w ramachif
ciele.) W niektórych przypadkach jest to nieco irytujące, więc jeśli naprawdę zależy ci na wprowadzeniu możliwie najmniejszej liczby zmiennych w każdym zakresie, możesz nadal chcieć użyćis
rzutowania.Nie sądzę, aby jakakolwiek z dotychczasowych odpowiedzi (w momencie rozpoczynania tej odpowiedzi!) Naprawdę wyjaśniała, gdzie warto jej użyć.
Nie rób tego:
Nie tylko sprawdza się to dwukrotnie, ale może sprawdzać różne rzeczy, jeśli
randomObject
jest to pole, a nie zmienna lokalna. Możliwe jest, że „if” przejdzie, ale rzutowanie zakończy się niepowodzeniem, jeśli inny wątek zmieni wartośćrandomObject
między nimi.Jeśli
randomObject
naprawdę powinno to być wystąpienieTargetType
, tzn. Jeśli tak nie jest, oznacza to błąd, to casting jest właściwym rozwiązaniem. To natychmiast zgłasza wyjątek, co oznacza, że nie wykonuje się żadnej pracy przy niepoprawnych założeniach, a wyjątek poprawnie pokazuje rodzaj błędu.Jeśli
randomObject
może to być instancjaTargetType
iTargetType
jest typem odwołania, użyj kodu w następujący sposób:Jeśli
randomObject
może być instancją typuTargetType
iTargetType
jest typem wartości, nie możemy użyć goas
razemTargetType
, ale możemy użyć typu zerowego:(Uwaga: obecnie jest to w rzeczywistości wolniejsze niż + cast . Myślę, że jest bardziej eleganckie i spójne, ale proszę bardzo.)
Jeśli naprawdę nie potrzebujesz przekonwertowanej wartości, ale musisz tylko wiedzieć, czy jest to instancja TargetType, a następnie
is
operator jest Twoim przyjacielem. W tym przypadku nie ma znaczenia, czy TargetType jest typem referencyjnym czy typem wartości.Mogą wystąpić inne przypadki dotyczące leków generycznych
is
są użyteczne (ponieważ możesz nie wiedzieć, czy T jest typem referencyjnym, czy nie, więc nie możesz użyć jako), ale są one stosunkowo niejasne.Prawie na pewno wcześniej użyłem
is
przypadku typu wartości, nie myśląc o użyciu typu zerowalnego ias
razem :)EDYCJA: Zauważ, że żadne z powyższych nie mówi o wydajności, poza przypadkiem typu wartości, w którym zauważyłem, że rozpakowanie do typu wartości zerowej jest w rzeczywistości wolniejsze - ale spójne.
Zgodnie z odpowiedzią naasking, zarówno rzutowanie jak i rzucanie jest zarówno szybkie jak i zerowe przy pomocy nowoczesnych JIT, jak pokazano w poniższym kodzie:
Na moim laptopie wszystkie działają w około 60ms. Dwie rzeczy do zapamiętania:
Nie martwmy się więc o wydajność. Martwmy się o poprawność i spójność.
Uważam, że zarówno rzutowanie, jak i rzutowanie jest niebezpieczne w przypadku zmiennych, ponieważ rodzaj wartości, do której się odnosi, może się zmienić z powodu innego wątku między testem a rzutowaniem. To byłaby dość rzadka sytuacja - ale wolałbym mieć konwencję, z której mogę konsekwentnie korzystać.
Twierdzę również, że kontrola „jak wtedy-zero” zapewnia lepszy rozdział obaw. Mamy jedno zdanie, które próbuje przekształcić, a następnie jedno zdanie, które wykorzystuje wynik. Is-and-cast lub is-and-as wykonuje test, a następnie kolejną próbę konwersji wartości.
Innymi słowy, czy ktoś kiedykolwiek napisałby:
Tak właśnie się dzieje - choć oczywiście w tańszy sposób.
źródło
if (randomObject is TargetType convertedRandomObject){ // Do stuff with convertedRandomObject.Value}
lub użyćswitch
/case
zobaczyć dokumenty„as” zwróci NULL, jeśli nie będzie możliwe przesłanie.
rzucanie wcześniej spowoduje wyjątek.
W przypadku wydajności zgłoszenie wyjątku jest zwykle bardziej kosztowne w czasie.
źródło
Oto kolejna odpowiedź, z pewnym porównaniem IL. Rozważ klasę:
Teraz spójrz na IL, którą wytwarza każda metoda. Nawet jeśli kody operacyjne nic dla ciebie nie znaczą, możesz zauważyć jedną zasadniczą różnicę - wywoływana jest isinst, po której następuje castclass w metodzie DirectCast. Więc dwa połączenia zamiast jednego w zasadzie.
Słowo kluczowe isinst vs. castclass
Ten post na blogu zawiera przyzwoite porównanie dwóch sposobów. Jego streszczenie to:
Ja osobiście zawsze używam As, ponieważ jest łatwy do odczytania i jest zalecany przez zespół programistów .NET (lub Jeffrey Richter i tak)
źródło
Jedną z bardziej subtelnych różnic między nimi jest to, że słowa kluczowego „as” nie można używać do rzutowania, gdy zaangażowany jest operator rzutowania:
Nie kompiluje się (chociaż myślę, że tak było w poprzednich wersjach) w ostatnim wierszu, ponieważ słowa kluczowe „as” nie uwzględniają operatorów rzutowania. Linia
string cast = (string)f;
działa jednak dobrze.źródło
as nigdy nie zgłasza wyjątku, jeśli nie może zamiast tego wykonać konwersji zwracając null ( ponieważ działa tylko na typach referencyjnych). Więc użycie as jest w zasadzie równoważne z
Natomiast rzutki w stylu C rzucają wyjątek, gdy konwersja nie jest możliwa.
źródło
Nie do końca odpowiedź na twoje pytanie, ale to, co uważam za ważny powiązany punkt.
Jeśli programujesz do interfejsu, nie powinno być potrzeby przesyłania. Mam nadzieję, że te obsady są bardzo rzadkie. Jeśli nie, prawdopodobnie musisz ponownie przemyśleć niektóre interfejsy.
źródło
Proszę zignorować rady Jona Skeeta, a mianowicie: unikać wzorca testowania i rzucania, tj .:
Pomysł, że kosztuje to więcej niż rzut i test zerowy, to MIT :
To mikrooptymalizacja, która nie działa. Przeprowadziłem kilka prawdziwych testów , a testowanie i przesyłanie jest w rzeczywistości szybsze niż porównanie rzutowania i zerowania, a także jest bezpieczniejsze, ponieważ nie masz możliwości posiadania pustego odwołania w zakresie poza rzutowaniem zawieść.
Jeśli chcesz mieć powód, dla którego testowanie i przesyłanie jest szybsze lub przynajmniej wolniejsze, istnieje prosty i złożony powód.
Proste: nawet naiwne kompilatory połączą dwie podobne operacje, takie jak testowanie i odlewanie, w jeden test i gałąź. test cast-and-null może wymusić dwa testy i odgałęzienie, jeden dla testu typu i konwersji na zero w przypadku niepowodzenia, jeden dla samego sprawdzenia null. Przynajmniej obie zoptymalizują się do pojedynczego testu i gałęzi, więc testowanie i rzucanie nie byłoby ani wolniejsze, ani szybsze niż rzutowanie i zerowanie.
Złożona: dlaczego testowanie i rzutowanie jest szybsze: testowanie rzutowe i zerowe wprowadza kolejną zmienną do zakresu zewnętrznego, którą kompilator musi śledzić pod kątem żywotności, i może nie być w stanie zoptymalizować tej zmiennej w zależności od stopnia złożoności kontroli przepływ jest. I odwrotnie, test-and-cast wprowadza nową zmienną tylko w ograniczonym zakresie, dzięki czemu kompilator wie, że zmienna jest martwa po wyjściu z zakresu, a zatem może lepiej zoptymalizować alokację rejestru.
PROSZĘ więc, niech ten „test„ rzuć i zeruj ”jest lepszy niż„ testuj i odrzuć ”poradę DIE. PROSZĘ. test-and-cast jest zarówno bezpieczniejszy, jak i szybszy.
źródło
ref
parametr. Jest bezpieczny dla zmiennych lokalnych, ale nie dla pól. Byłbym zainteresowany uruchomieniem testów, ale kod podany w poście na blogu nie jest kompletny. Zgadzam się z brakiem mikrooptymalizacji, ale nie sądzę, aby dwukrotne użycie tej wartości było bardziej czytelne lub eleganckie niż użycie „as” i testu nieważności. (Zdecydowanie użyłbym prostej obsady, a nie „as” po b, btw.)Jeśli rzutowanie się nie powiedzie, słowo kluczowe „as” nie zgłasza wyjątku; zamiast tego ustawia zmienną na null (lub na wartość domyślną dla typów wartości).
źródło
To nie jest odpowiedź na pytanie, ale komentarz do przykładu kodu pytania:
Zwykle nie trzeba rzutować obiektu z np. IMyInterface na MyClass. Wspaniałą rzeczą w interfejsach jest to, że jeśli weźmiesz obiekt jako dane wejściowe, które implementuje interfejs, nie musisz się martwić, jaki rodzaj obiektu otrzymujesz.
Jeśli rzucisz IMyInterface na MyClass, to już zakładasz, że otrzymujesz obiekt typu MyClass i nie ma sensu używać IMyInterface, ponieważ jeśli karmisz swój kod innymi klasami, które implementują IMyInterface, spowoduje to uszkodzenie twojego kodu ...
Teraz moja rada: jeśli twoje interfejsy są dobrze zaprojektowane, możesz uniknąć wielu rzutów.
źródło
as
Operator może być używany tylko w typach referencyjnych, to nie może być przeciążona, a powrócinull
, jeśli operacja się nie powiedzie. Nigdy nie rzuci wyjątku.Przesyłanie może być zastosowane na dowolnym kompatybilnym typie, może być przeciążone i spowoduje wyjątek, jeśli operacja się nie powiedzie.
Wybór zastosowania zależy od okoliczności. Przede wszystkim chodzi o to, czy chcesz zgłosić wyjątek w przypadku nieudanej konwersji.
źródło
Moja odpowiedź dotyczy tylko prędkości w przypadkach, gdy nie sprawdzamy typu i nie sprawdzamy wartości zerowych po rzuceniu. Dodałem dwa dodatkowe testy do kodu Jona Skeeta:
Wynik:
Nie próbuj koncentrować się na szybkości (tak jak ja), ponieważ wszystko to jest bardzo szybkie.
źródło
as
konwersja (bez sprawdzania błędów) przebiegała około 1-3% szybciej niż rzutowanie (około 540 ms wobec 550 ms przy 100 milionach iteracji). Ani nie złoży ani nie złamie twojej aplikacji.Oprócz wszystkiego, co już tu ujawniłem, właśnie natrafiłem na praktyczną różnicę, którą moim zdaniem warto zauważyć, między jawnym castingiem
w porównaniu z użyciem
as
operatora.Oto przykład:
Konkluzja: GenericCaster2 nie będzie działać z typami struktur. GenericCaster będzie.
źródło
W przypadku korzystania z pakietu Office PIA kierowania .NET Framework 4.X należy użyć jako hasła, w przeciwnym razie nie będzie skompilować.
Przesyłanie jest jednak prawidłowe podczas celowania w .NET 2.0:
Podczas celowania w .NET 4.X błędy to:
błąd CS0656: Brak wymaganego kompilatora członek „Microsoft.CSharp.RuntimeBinder.Binder.Convert”
błąd CS0656: Brak wymaganego kompilatora element członkowski „Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo.Create”
źródło
Słowo
as
kluczowe działa tak samo, jak jawne rzutowanie między zgodnymi typami referencyjnymi, z tą różnicą, że nie powoduje wyjątku w przypadku niepowodzenia konwersji. Daje raczej wartość zerową w zmiennej docelowej. Ponieważ wyjątki są bardzo drogie pod względem wydajności, uważa się je za znacznie lepszą metodę odlewania.źródło
To, co wybierzesz, zależy od tego, co jest wymagane. Wolę jawne castingi
ponieważ jeśli obiekt powinien być typu IMyInterface, a nie jest - to zdecydowanie problem. Lepiej jest otrzymać błąd jak najwcześniej, ponieważ dokładny błąd zostanie naprawiony zamiast naprawiać jego efekt uboczny.
Ale jeśli masz do czynienia z metodami, które akceptują
object
jako parametr, musisz sprawdzić jego dokładny typ przed wykonaniem jakiegokolwiek kodu. W takim przypadkuas
przydatne byłoby uniknięcieInvalidCastException
.źródło
Zależy, czy po sprawdzeniu „as” chcesz sprawdzić wartość null, czy wolisz, aby aplikacja zgłosiła wyjątek?
Moją ogólną zasadą jest, jeśli zawsze oczekuję, że zmienna będzie typu, którego oczekuję w momencie, gdy chcę użyć rzutowania. Jeśli możliwe jest, że zmienna nie będzie rzutować na to, czego chcę i jestem przygotowany do obsługi wartości zerowych przy użyciu as, użyję jako.
źródło
Spójrz na te linki:
pokazują pewne szczegóły i testy wydajności.
źródło
Problem PO jest ograniczony do konkretnej sytuacji rzutowania. Tytuł obejmuje znacznie więcej sytuacji.
Oto przegląd wszystkich istotnych sytuacji castingowych, o których obecnie mogę myśleć:
źródło