Boş kontrol olmasa bile, oyuncu kadrosu yerine “as” kullanmak mantıklı mı? [kapalı]


351

Geliştirme blogları, çevrimiçi kod örnekleri ve (son zamanlarda) bir kitap bile, böyle kod hakkında tökezlemeye devam:

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

veya daha da kötüsü:

(x as T).SomeMethod();

Bu benim için bir anlam ifade etmiyor. Eğer eminseniz xtiptedir T, doğrudan döküm kullanmalısınız: (T)x. Emin değilseniz kullanabilirsiniz, asancak nullbir işlem yapmadan önce kontrol etmeniz gerekir . Yukarıdaki kodun yaptığı tek şey (yararlı) 'yı InvalidCastException(işe yaramaz) dönüştürmektir NullReferenceException.

Bunun asanahtar kelimeyi açıkça kötüye kullandığını düşünen tek kişi ben miyim ? Yoksa bariz bir şey mi kaçırdım ve yukarıdaki model gerçekten mantıklı mı?


56
Görmek daha komik olurdu (S olarak öpmek) .SteveIsSuchA (); Ama katılıyorum, bu bir istismar.
SwDevMan81

5
Yazmaktan çok daha havalı ((T)x).SomeMethod()değil mi? ;) (şaka yapıyorum, tabii ki haklısın!)
Lucero

8
@P Baba Ben katılmıyorum, mükemmel iyi bir soru (bu kod deseni gerçekten mantıklı mı) ve çok yararlı. Soruyu + 1'lemek ve kapatmak için oy veren herkese kaşlarını çatmak.
MarkJ

9
Lucerno haklı, bu kodlama modeli parantezlerden kaçınmaya çalışılarak uyarıldı. Lisp'e maruz kaldıktan sonra tedavi edilemez.
Hans Passant

13
Optimize edilmiş kod (f as T).SomeMethod():;)
MSalters

Yanıtlar:


252

Anlayışınız doğru. Bana mikro optimizasyon yapmaya çalışıyor gibi görünüyor. Türden emin olduğunuzda normal bir döküm kullanmalısınız. Daha mantıklı bir istisna oluşturmanın yanı sıra, hızlı bir şekilde başarısız olur. Türle ilgili varsayımınız yanlışsa, programınız hemen başarısız olur ve gelecekte bir NullReferenceExceptionveya bir veya ArgumentNullExceptionmantıklı bir hata beklemek yerine hemen hatanın nedenini görebilirsiniz . Genel olarak, bir yerde asbir nullkontrolün ardından gelmeyen bir ifade kod kokusudur.

Öte yandan, dökümden emin değilseniz ve başarısız olmasını bekliyorsanız as, bir try-catchblokla sarılmış normal bir döküm yerine kullanmalısınız . Ayrıca, astip kontrolü ve ardından oyuncu kadrosu üzerinden kullanılması önerilir. Onun yerine:

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

hangi bir oluşturur isinsttalimat için isanahtar kelime ve castclasstalimat döküm (etkili iki defa döküm gerçekleştirmek) için, kullanmak gerekir:

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

Bu sadece bir isinsttalimat oluşturur . Bir yarış koşulu, değişkenin isdenetim başarılı olduktan ve döküm hattında başarısız olduktan sonra türünü değiştirmesine neden olabileceğinden, önceki yöntemin çok iş parçacıklı uygulamalarda potansiyel bir kusuru vardır . İkinci yöntem bu hataya eğilimli değildir.


Aşağıdaki kodun üretim kodunda kullanılması önerilmez . C # 'da böyle temel bir yapıdan gerçekten nefret ediyorsanız, VB'ye veya başka bir dile geçmeyi düşünebilirsiniz.

Birinin döküm sözdiziminden umutsuzca nefret etmesi durumunda, oyuncuları taklit etmek için bir uzatma yöntemi yazabilir:

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

ve düzgün bir [?] sözdizimi kullanın:

obj.To<SomeType>().SomeMethod()

5
Bence yarış durumu alakasız. Bu sorunu yaşıyorsanız, kodunuz iş parçacığı için güvenli değildir ve bunu çözmek için "as" anahtar sözcüğünü kullanmaktan daha güvenilir yollar vardır. Cevabın geri kalanı için +1.
RMorrisey

10
@RMorrisey: Aklımda en az bir örnek var: cacheBaşka bir iş parçacığının ayarlayarak geçersiz kılmaya çalıştığı bir nesneniz olduğunu varsayalım null. Kilitsiz senaryolarda bu tür şeyler ortaya çıkabilir.
Mehrdad Afshari

10
is + cast, FxCop'tan "Gereksiz yere yayınlama" uyarısını tetiklemek için yeterlidir: msdn.microsoft.com/en-us/library/ms182271.aspx Yapıyı önlemek için yeterli neden olmalıdır.
David Schmitt

2
Genişletme yöntemlerini kullanmaktan kaçınmalısınız Object. Yöntemin bir değer türünde kullanılması, gereksiz yere kutulanmasına neden olur.
MgSam

2
@MgSam Açıkçası, böyle bir kullanım durumu Toburada sadece yöntem için anlam ifade etmez , çünkü değer türleri için yine de boks içeren miras hiyerarşisine dönüşür. Elbette, tüm fikir ciddi olmaktan çok teoriktir.
Mehrdad Afshari

42

IMHO, asbir nullçekle birleştirildiğinde mantıklı :

var y = x as T;
if (y != null)
    y.SomeMethod();

40

'As' kullanılması, kullanıcı tanımlı dönüşümleri uygulamazken, yayın uygun olduğunda dönüşümleri kullanır. Bu bazı durumlarda önemli bir fark olabilir.


5
Bunu hatırlamak önemlidir. Eric Lippert burada devam ediyor: blogs.msdn.com/ericlippert/archive/2009/10/08/…
P Daddy

5
Güzel yorum, P! Kodunuz bu ayrımı bağlı ise, ben gelecekte gece geç saatlerde hata ayıklama oturumu olduğunu söyleyebilirim.
TrueWill

36

Bu konuda biraz yazdım:

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

Ne demek istediğini anlıyorum. Ve itişine katılıyorum: bir döküm operatörü "Bu nesnenin bu tipe dönüştürülebileceğinden eminim ve eğer yanılıyorsam bir istisna riski taşıyorum", oysa "as" operatörü "Bu nesnenin bu türe dönüştürülebileceğinden emin değilim; yanılıyorsam bana boş ver".

Ancak, ince bir fark var. (x olarak T). "(x'nin sadece bir T'ye dönüştürülebileceğini değil, aynı zamanda bunu yapmanın yalnızca referans veya kutudan çıkarma dönüşümlerini içerdiğini ve ayrıca x'in boş olmadığını da biliyorum). Bu, ((T) x) 'den farklı bilgileri iletir. () Ne olursa olsun ve belki de kodun yazarı budur.


1
Son cümlenizde kod yazarının spekülatif savunmasına katılmıyorum. ((T)x).Whatever() Ayrıcax boş olması amaçlanmayan iletişim kurar ve bir yazarın genellikle dönüşümün Tyalnızca referans mı yoksa kutudan çıkarma dönüşümleriyle mi gerçekleşeceğini yoksa kullanıcı tanımlı mı yoksa temsili değiştiren bir dönüşüm mü gerektirdiğini umduğuna şüphe ederim . Sonuçta, eğer tanımlarsam public static explicit operator Foo(Bar b){}, o zaman benim niyetim Baruyumlu olarak kabul edilir Foo. Bu dönüşümden kaçınmak nadirdir.
P Daddy

4
Belki de kod yazarlarının çoğu bu ince ayrımı yapmazdı. Şahsen olabilirim, ama olsaydım, bu etkiye bir yorum eklerdim.
Eric Lippert

16

Ben sık sık bu yanıltıcı makaleye referans olarak "olarak" döküm daha hızlı kanıt olarak gördüm .

Bu makalenin daha belirgin yanıltıcı yönlerinden biri, neyin ölçülmekte olduğunu göstermeyen grafiktir: Başarısız dökümleri ölçtüğünden şüpheleniyorum (burada "as", istisna olmadığı için çok daha hızlıdır).

Ölçümleri yapmak için zaman ayırırsanız, dökümün beklediğiniz gibi döküm başarılı olduğunda "olduğu gibi" daha hızlı olduğunu görürsünüz .

Bunun, cast anahtarının as anahtar kelimesinin "kargo kültü" kullanımının bir nedeni olabileceğinden şüpheleniyorum.


2
Bağlantı için teşekkürler, bu çok ilginç. Yazıyı anlaşılan nasıl itibaren, o does olmayan istisna durum karşılaştırın. Bununla birlikte, makale .net 1.1 için yazılmıştır ve yorumlar bunun .net 2.0'da değiştiğine işaret etmektedir: Performans artık neredeyse eşittir, önek dökümü biraz daha hızlıdır.
Heinzi

1
Makale, istisna dışı durumu karşılaştırdığını ima ediyor, ancak uzun zaman önce bazı testler yaptım ve .NET 1.x ile bile iddia edilen sonuçlarını çoğaltamadım. Ve makale karşılaştırmayı çalıştırmak için kullanılan kodu sağlamadığından, karşılaştırılanları söylemek mümkün değildir.
Joe

"Kargo kült" - mükemmel. Tüm bilgi için "Cargo Cult Science Richard Feynman" bölümüne bakın.
Bob Denny

11

Doğrudan yayın, asanahtar kelimeden daha fazla bir parantez gerektirir . Dolayısıyla, türün ne olduğundan% 100 emin olduğunuzda bile, görsel dağınıklığı azaltır.

Yine de istisna şey üzerinde anlaştılar. Ama en azından benim asiçin, nulldaha sonra kontrol etmek için çoğu kaynama kullanımı, bir istisna yakalamaktan daha güzel buluyorum.


8

"As" kullandığım zamanın% 99'u, gerçek nesne türünün ne olduğundan emin olmadığım zaman

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

ve açık döküm istisnaları yakalamak veya "is" kullanarak iki kez döküm yapmak istemiyorum:

//I don't like this
if(obj is T){
  var x = (T)obj; 
}

8
Az önce doğru kullanım durumunu anlattınız as. Diğer% 1 nedir?
P Daddy

Yazım hatası mı? =) Bu tam kod snippet'ini kullandığım zamanın% 99'unu kastediyorum , bazen bir yöntem çağrısında veya başka bir yerde "as" kullanabilirim.
Max Galkin

D'oh, ve bu ikinci popüler cevaptan daha az faydalı mı ???
Max Galkin

2
+1 Bunu
söylemeyi

8

Çünkü insanlar görünüşlerini seviyorlar, çok okunabilirler.

Şunu kabul edelim: C benzeri dillerde döküm / dönüşüm operatörü oldukça korkunç, okunabilirlik açısından akıllıca. C # ya Javascript sözdizimini benimserse daha iyi istiyorum:

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

Veya bir tooperatör tanımlayın as;

object o = 1;
int i = o to int;

Bildiğiniz gibi, bahsettiğiniz JavaScript sözdizimine C ++ 'da da izin verilir.
P Daddy

@PDaddy: Yine de doğrudan% 100 uyumlu bir alternatif sözdizimi değil ve bu şekilde tasarlanmamıştır (operatör X'e göre dönüşüm oluşturucu)
Ruben Bartelink

Ben dynamic_cast<>()(ve benzeri) C ++ sözdizimini kullanmayı tercih ederim . Çirkin bir şey yapıyorsun, çirkin görünmeli.
Tom Hawtin - tackline

5

İnsanlar o askadar çok seviyor ki istisnalardan kendilerini güvende hissetmelerini sağlıyor ... Bir kutudaki garanti gibi. Bir adam kutuya süslü bir garanti veriyor çünkü içeride sıcak ve kızarmış hissetmenizi istiyor. O küçük kutuyu geceleri yastığının altına koyduğunu düşünüyorsun, Garanti Perisi aşağı inip çeyreği terk edebilir, değil mi Ted?

Konuya dönelim ... doğrudan yayın kullanırken, geçersiz bir yayın istisnası olasılığı vardır . Bu yüzden insanlar astüm döküm ihtiyaçlarına battaniye bir çözüm olarak başvuruyorlar çünkü as(tek başına) asla bir istisna atmayacaklar. Ancak bununla ilgili komik şey, verdiğiniz örnekte (x as T).SomeMethod();, boş bir referans istisnası için geçersiz bir döküm istisnasıyla işlem yaptığınızdır. Bu istisnayı gördüğünüzde gerçek sorunu gizler.

Genelde asçok fazla kullanmıyorum . isTesti tercih ederim, çünkü bana göre, daha okunabilir görünüyor ve bir oyuncu kadrosunu denemekten ve null olup olmadığını kontrol etmekten daha mantıklı.


2
"Ben is testini tercih ederim" - "is" ve ardından kadro elbette "as" dan daha yavaştır, ardından null için bir test (tıpkı "IDictionary.ContainsKey" gibi, ardından indeksleyiciyi kullanarak kayıt silme işlemi "IDictionary.TryGetValue" den daha yavaştır. "). Ancak daha okunabilir bulursanız, şüphesiz fark nadiren önemlidir.
Joe

Orta kısımdaki önemli açıklama, insanların askendilerini güvende hissetmelerini sağladığı için battaniye çözümü olarak nasıl başvurduklarıdır .
Bob

5

Bu benim en sevdiğim şeylerden biri olmalı .

Stroustrup'un D&E ve / veya şu anda bulamadığım bazı blog yazısı to, https://stackoverflow.com/users/73070/johannes-rossel (yani, semantik asile aynı sözdizimi) tarafından belirtilen noktayı ele alan bir operatör kavramını tartışıyor. DirectCast).

Bunun uygulanamamasının nedeni, alçının ağrıya neden olması ve çirkin olması, böylece onu kullanmaktan uzaklaşmanızdır.

'Zeki' programcıların (genellikle kitap yazarları (Juval Lowy IIRC)) asbu tarzda kötüye kullanarak bu konuda adım atması üzücü (C ++ asmuhtemelen bu nedenle bir teklif sunmaz ).

VB bile, bir TryCastveya seçmenize DirectCastve karar vermenize neden olan tekdüze bir sözdizimine sahip olmak için daha fazla tutarlılığa sahiptir !


+1. Muhtemelen sentaks değil DirectCast davranış demek istedin .
Heinzi

@Heinzi: +1 için Ta. İyi bir nokta. Bir semantics
zeki

C # 'ın C, C ++ veya Java ile uyumluluk iddiası olmadığı göz önüne alındığında, kendimi bu dillerden ödünç aldıkları bazı şeylere baktım. "Bunun bir X olduğunu biliyorum" ve "bunun bir X olmadığını biliyorum, ama bir olarak temsil edilebilir" ifadesinin ötesine geçiyor, "Bunun bir X olmadığını biliyorum ve gerçekten bir olarak temsil edilemeyebilir" , ama yine de bana bir X ver. " Ben bir sığabilecek kesin bir değeri temsil etmedi , ancak verim -1 sahip olmak çirkin double-to-intolduğunu başarısız bir döküm için yararlı olduğunu görebiliyordu . doubleInt32(int)-1.5
supercat

@supercat Evet, ama dil tasarımı kolay biliyoruz ama hepimizin bildiği gibi - C # nullables'e dahil olan takas kümesine bakın. Bilinen tek panzehir, Derinlik basımlarında C # 'ın düzenli olarak okunmasıdır :) Neyse ki F #' ın nüanslarını bu günlerde anlamakla daha çok ilgileniyorum ve bu konularda çok daha aklı başındayım.
Ruben Bartelink

@RubenBartelink: Sıfırlanabilir türlerin tam olarak hangi problemleri çözmesi gerektiği konusunda net değilim, ancak çoğu durumda MaybeValid<T>iki ortak alana sahip olmanın daha iyi olacağını IsValidve Valueuygun görüldüğü gibi hangi kodun yapabileceğini düşünürdüm. Bu, örneğin MaybeValid<TValue> TryGetValue(TKey key) { var ret = default(MaybeValid<TValue>); ret.IsValid = dict.TryGetValue(key, out ret.Value); return ret; }; Sadece tasarruf en az iki kopyalama işlemleri ile karşılaştırıldığında olacağını değil Nullable<T>, ama birlikte değer de olabilir herhangi tip T--not sadece sınıfları.
supercat

2

asAnahtar kelimenin dynamic_castC ++ 'dan daha zarif görünümlü bir versiyonu olarak düşünülebileceğine inanıyorum .


C # düz bir döküm dynamic_castC ++ daha benzer olduğunu söyleyebilirim .
P Daddy

C # düz döküm C ++ static_cast daha eşdeğer olduğunu düşünüyorum.
Andrew Garrison

2
@Ruben Bartelink: Yalnızca işaretçilerle null değerini döndürür. Mümkün olduğunda kullanmanız gereken referanslarla atar std::bad_cast.
P Daddy

1
@Andrew Garrison: static_castçalışma zamanı türü denetimi gerçekleştirmez. C # 'da buna benzer bir döküm yok.
P Daddy

Ne yazık ki, sadece işaretçiler üzerinde kullandığım için referanslarda dökümleri bile kullanabileceğinizi bilmiyordum, ama P Daddy kesinlikle doğru!
Andrew Garrison

1

Teknik bir sebep olmadan muhtemelen daha popüler, ancak daha kolay okunması ve daha sezgisel olması nedeniyle. (Sadece soruyu cevaplamaya çalışmayı daha iyi hale getirdiğini söylememek)


1

"As" kullanmanın bir nedeni:

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

Yerine (kötü kod):

if (obj is T)
{
     //bang, some other thread changes obj to another type...
     action((T)obj); //InvalidCastException
}

2
Bu çirkin ırk kondisyonunuz varsa, daha büyük sorunlarınız var (ama diğerleriyle birlikte gitmek için güzel bir örnek katılıyorum
Ruben Bartelink

-1, bu bir yanlışlığa neden olur. Diğer iş parçacıkları obj türünü değiştiriyorsa, hala sorunlarınız var. "// hala çalışıyor" iddiasının gerçekte olması pek olası değildir, çünkü t, T'ye bir işaretçi olarak kullanılacaktır, ancak artık T olmayan bir belleğe işaret eder. Diğer iş parçacığı, (t) işlemi devam ederken obj.
Stephen C. Steel

5
@Stephen C. Steel: Çok karışık görünüyorsunuz. Türünü değiştirmek, başka bir nesneye başvuru yapmak objiçin objdeğişkenin kendisini değiştirmek anlamına gelir . Başlangıçta başvurulan nesnenin bulunduğu belleğin içeriğini değiştirmez obj. Bu orijinal nesne değişmeden kalır ve tdeğişken yine de ona referans tutar.
P Daddy


1
@P Daddy - Doğru olduğunu düşünüyorum ve yanılmışım: obj bir T nesnesinden bir T2 nesnesine geri sekseydi, o zaman hala eski T nesnesini işaret ederdi. T hala eski nesneye başvurduğundan, çöp toplanamaz, bu nedenle eski T nesnesi geçerli kalır. Irk durumu dedektör devrelerim, dinamik_cast kullanan benzer kodun potansiyel bir sorun olacağı C ++ üzerinde eğitildi.
Stephen C. Steel
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.