"Catch, when" ile istisnaları yakalama


97

C # 'da bir catch işleyicinin belirli bir koşul karşılandığında çalıştırılmasına izin veren bu yeni özelliğe rastladım.

int i = 0;
try
{
    throw new ArgumentNullException(nameof(i));
}
catch (ArgumentNullException e)
when (i == 1)
{
    Console.WriteLine("Caught Argument Null Exception");
}

Bunun ne zaman yararlı olabileceğini anlamaya çalışıyorum.

Bir senaryo şuna benzer bir şey olabilir:

try
{
    DatabaseUpdate()
}
catch (SQLException e)
when (driver == "MySQL")
{
    //MySQL specific error handling and wrapping up the exception
}
catch (SQLException e)
when (driver == "Oracle")
{
    //Oracle specific error handling and wrapping up of exception
}
..

ancak bu yine aynı işleyici içinde yapabileceğim ve sürücünün türüne bağlı olarak farklı yöntemlere delege edebileceğim bir şey. Bu, kodun anlaşılmasını kolaylaştırır mı? Muhtemelen hayır.

Aklıma gelen başka bir senaryo da şöyle bir şey:

try
{
    SomeOperation();
}
catch(SomeException e)
when (Condition == true)
{
    //some specific error handling that this layer can handle
}
catch (Exception e) //catchall
{
    throw;
}

Yine bu benim beğenebileceğim bir şey:

try
{
    SomeOperation();
}
catch(SomeException e)
{
    if (condition == true)
    {
        //some specific error handling that this layer can handle
    }
    else
        throw;
}

'Yakala, ne zaman' özelliğini kullanmak, istisna işlemeyi daha hızlı hale getirir, çünkü işleyici bu şekilde atlanır ve yığın çözülme, işleyicideki belirli kullanım durumlarının işlenmesine kıyasla çok daha erken olabilir mi? Bu özelliğe daha iyi uyan, insanların daha sonra iyi bir uygulama olarak benimseyebilecekleri belirli kullanım örnekleri var mı?


9
whenİstisnanın kendisine erişmesi gerekiyorsa kullanışlıdır
Tim Schmelter

1
Ancak bu, işleyici bloğunun kendi içinde de yapabileceğimiz bir şey. 'Biraz daha organize bir kod' dışında herhangi bir fayda var mı?
MS Srikkanth

3
Ama sonra istemediğiniz istisnayı zaten halletmişsinizdir. Ya bunun içinde başka bir yerde yakalamak istersen try..catch...catch..catch..finally?
Tim Schmelter

4
@ user3493289: Bu argümanı takiben, istisna işleyicilerinde otomatik tip kontrollerine de ihtiyacımız yok: Sadece catch (Exception ex)tipe izin verebilir , kontrol edebiliriz ve throwbaşka türlü. Biraz daha organize kod (kod gürültüsünden kaçınma) tam olarak bu özelliğin neden var olduğudur. (Bu aslında birçok özellik için doğrudur.)
Heinzi

2
@TimSchmelter Teşekkürler. Bir cevap olarak gönder, ben de kabul edeceğim. Bu durumda gerçek senaryo, 'işleme koşulu istisnaya bağlıysa' olur, o zaman bu özelliği kullanın /
MS Srikkanth

Yanıtlar:


122

Yakalama blokları zaten istisna türüne göre filtreleme yapmanıza izin verir :

catch (SomeSpecificExceptionType e) {...}

whenFıkra Eğer jenerik ifadeleri için bu filtreyi genişletme olanağını sağlar.

Böylece kullandığınız whendurumlarda için maddeyi tipi istisna istisna burada ya da değil işleneceğini belirlemek için ayrı yeterli değildir.


Yaygın bir kullanım durumu, aslında birden çok, farklı türde hata için bir sarmalayıcı olan istisna türleridir .

İşte gerçekten kullandığım bir durum (zaten bu özelliğe oldukça uzun süredir sahip olan VB'de):

try
{
    SomeLegacyComOperation();
}
catch (COMException e) when (e.ErrorCode == 0x1234)
{
    // Handle the *specific* error I was expecting. 
}

Aynı özelliği SqlExceptionolan için de aynı ErrorCode. Alternatif şunun gibi bir şey olabilir:

try
{
    SomeLegacyComOperation();
}
catch (COMException e)
{
    if (e.ErrorCode == 0x1234)
    {
        // Handle error
    }
    else
    {
        throw;
    }
}

Bu tartışmalı olarak daha az zarif ve yığın izini biraz bozuyor .

Ek olarak, aynı try-catch bloğunda aynı istisna türünden iki kez bahsedebilirsiniz :

try
{
    SomeLegacyComOperation();
}
catch (COMException e) when (e.ErrorCode == 0x1234)
{
    ...
}
catch (COMException e) when (e.ErrorCode == 0x5678)
{
    ...
}

whenKoşul olmadan bu mümkün olmazdı .


2
İkinci yaklaşım da onu farklı bir şekilde yakalamaya izin vermiyor catch, değil mi?
Tim Schmelter

@TimSchmelter. Doğru. Tüm COMExceptions'ı aynı blokta işlemeniz gerekir .
Heinzi

While, whenaynı istisna türünü birden çok kez işlemenizi sağlar. Bunu da belirtmelisiniz çünkü bu çok önemli bir fark. Olmazsa whenbir derleyici hatası alırsınız.
Tim Schmelter

1
Bana kalırsa, "Özetle:" yi takip eden kısım cevabın ilk satırı olmalı.
CompuChip

1
@ user3493289: Çirkin kodda genellikle durum böyledir. "İlk etapta bu karmaşanın içinde olmamalıyım, kodu yeniden tasarlamam gerektiğini" düşünüyorsunuz ve ayrıca "bu tasarımı zarif bir şekilde desteklemenin, dili yeniden tasarlamanın bir yolu olabilir" diye düşünüyorsunuz. Bazı durumlarda daha az çirkin kılan bir şey daha fazla eşik :-) içinde halletmek sağlar, böylece bu durumda olmak yakalamak hükümlerin sizin setini istiyorum nasıl çirkin için eşik bir tür, orada
Steve Jessop

38

Roslyn'in wiki'sinden (vurgu benim):

İstisna filtreleri , yığını zarar görmeden bıraktıkları için yakalama ve yeniden fırlatma yerine tercih edilir . İstisna daha sonra yığının atılmasına neden oluyorsa, en son yeniden atıldığı yeri değil, başlangıçta nereden geldiğini görebilirsiniz.

Ayrıca, yan etkiler için istisna filtreleri kullanmak yaygın ve kabul gören bir "kötüye kullanım" biçimidir; örneğin günlük kaydı. Onlar yapabilirsiniz seyrini müdahale olmadan “tarafından uçan” bir istisna inceleyin . Bu durumlarda, filtre genellikle yan etkileri yürüten yanlış dönen bir yardımcı işleve çağrı olacaktır:

private static bool Log(Exception e) { /* log it */ ; return false; }

… try { … } catch (Exception e) when (Log(e)) { }

İlk nokta göstermeye değer.

static class Program
{
    static void Main(string[] args)
    {
        A(1);
    }

    private static void A(int i)
    {
        try
        {
            B(i + 1);
        }
        catch (Exception ex)
        {
            if (ex.Message != "!")
                Console.WriteLine(ex);
            else throw;
        }
    }

    private static void B(int i)
    {
        throw new Exception("!");
    }
}

Bunu, istisnaya ulaşılıncaya kadar WinDbg'de çalıştırırsak ve kullanarak yığını yazdırırsak, şunun !clrstack -i -açerçevesini göreceğiz A:

003eef10 00a7050d [DEFAULT] Void App.Program.A(I4)

PARAMETERS:
  + int i  = 1

LOCALS:
  + System.Exception ex @ 0x23e3178
  + (Error 0x80004005 retrieving local variable 'local_1')

Ancak, programı kullanmak için değiştirirsek when:

catch (Exception ex) when (ex.Message != "!")
{
    Console.WriteLine(ex);
}

Yığının ayrıca B'nin çerçevesini içerdiğini göreceğiz :

001af2b4 01fb05aa [DEFAULT] Void App.Program.B(I4)

PARAMETERS:
  + int i  = 2

LOCALS: (none)

001af2c8 01fb04c1 [DEFAULT] Void App.Program.A(I4)

PARAMETERS:
  + int i  = 1

LOCALS:
  + System.Exception ex @ 0x2213178
  + (Error 0x80004005 retrieving local variable 'local_1')

Bu bilgi, çökme dökümlerinde hata ayıklarken çok yararlı olabilir.


8
Bu beni şaşırtıyor. Yığını da zarar görmeden bırakmayacak mı throw;(aksine throw ex;)? Yan etki için +1. Bunu onayladığımdan emin değilim, ama bu tekniği bilmek güzel.
Heinzi

14
Yanlış değil - bu, yığın izleme anlamına gelmez - yığının kendisine atıfta bulunur. Yığına bir hata ayıklayıcıda (WinDbg) bakarsanız ve kullanmış olsanız bile throw;, yığın çözülür ve parametre değerlerini kaybedersiniz.
Eli Arbel

1
Bu, dökümlerde hata ayıklarken son derece yararlı olabilir.
Eli Arbel

3
@Heinzi Cevabımı , yığın izini biraz değiştirdiğini ve çok değiştirdiğini görebileceğiniz başka bir başlıkta görün . throw;throw ex;
Jeppe Stig Nielsen

1
Kullanımı throwyığın izini biraz bozar. Kullanırken Hat numaraları farklıdır throwaksine when.
Mike Zboray

7

Bir istisna atıldığında, istisna işlemenin ilk geçişi , yığını çözmeden önce istisnanın nerede yakalanacağını tanımlar ; "catch" konumu belirlendiğinde / olduğunda, tüm "nihayet" blokları çalıştırılır (bir istisna "nihayet" bloğundan kaçarsa, önceki istisnanın işlenmesinin terk edilebileceğini unutmayın). Bu gerçekleştiğinde, kod "yakalama" noktasında çalışmaya devam edecektir.

Bir işlev içinde "ne zaman" ın parçası olarak değerlendirilen bir kesme noktası varsa, bu kesme noktası herhangi bir yığın çözülmeden önce yürütmeyi askıya alır; tersine, "yakalama" noktasındaki bir kesme noktası, yalnızca tüm finallyişleyiciler çalıştıktan sonra yürütmeyi askıya alır .

Son olarak, fooaramanın 23 ve 27. satırları ve 23. barsatırdaki çağrı, foo57. satırda yakalanan ve yeniden oluşturulan bir istisna atarsa , yığın izleme istisnanın bar57. satırdan [yeniden atmanın yeri] çağrılırken meydana geldiğini önerecektir. , istisnanın 23 numaralı hatta mı yoksa 27 numaralı hat çağrısında mı meydana geldiğine dair herhangi bir bilgiyi yok etme. Kullanılması whenilk etapta bir istisna yakalamak önlemek için bu tür rahatsızlığı önler.

BTW, hem C # hem de VB.NET'te sinir bozucu derecede garip olan kullanışlı bir model, bir fonksiyonun normal olarak tamamlanıp tamamlanmadığını belirlemek için whenbir finallycümle içinde kullanılabilecek bir değişken ayarlamak için bir cümle içinde bir fonksiyon çağrısı kullanmaktır. meydana gelen herhangi bir istisnayı "çözme" umudu yoktur, ancak yine de buna dayalı olarak harekete geçmelidir. Örneğin, bir fabrika yönteminde, kaynakları kapsayan bir nesneyi döndürmesi beklenen bir istisna atılırsa, elde edilen tüm kaynakların serbest bırakılması gerekir, ancak temeldeki istisna, arayana kadar süzülmelidir. Bunu anlamsal olarak (sözdizimsel olarak olmasa da) ele almanın en temiz yolu, birfinallyblok bir istisnanın meydana gelip gelmediğini kontrol edin ve eğer öyleyse, artık iade edilmeyecek nesne adına elde edilen tüm kaynakları serbest bırakın. Temizleme kodunun istisnaya neden olan koşulu çözme umudu olmadığından, gerçekten çözmemeli catch, yalnızca ne olduğunu bilmesi gerekir. Gibi bir işlevi çağırmak:

bool CopySecondArgumentToFirstAndReturnFalse<T>(ref T first, T second)
{
  first = second;
  return false;
}

bir whencümle içinde yer alan , fabrika işlevinin bir şey olduğunu bilmesini mümkün kılacaktır.

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.