Tutarlılık-kenara bir yana, kodumuzu yeniden düzenlemeye gerek kalmadan hata işlemesiyle kodlayabilmemiz mantıklı olmaz mı?
Buna cevap vermek için, bir değişkenin kapsamından daha fazlasına bakmak gerekir .
Değişkeni try bloğunda bildirmek - derleyiciye ve insan okuyuculara - yalnızca bu bloğun içinde anlamlı olduğunu ifade eder. Derleyicinin bunu zorlaması yararlıdır.
Değişkenin try bloğundan sonra kapsamda olmasını istiyorsanız, bunu bloğun dışında ilan edebilirsiniz:
var zerothVariable = 1_000_000_000_000L;
int firstVariable;
try {
// Change checked to unchecked to allow the overflow without throwing.
firstVariable = checked((int)zerothVariable);
}
catch (OverflowException e) {
Console.Error.WriteLine(e.Message);
Environment.Exit(1);
}
Bu, değişkenin try bloğu dışında anlamlı olabileceğini ifade eder. Derleyici buna izin verecektir.
Ancak, değişkenleri bir try bloğuna koyduktan sonra kapsamda tutmanın genellikle yararlı olmayacağının başka bir nedenini de gösterir. C # derleyicisine kesin atama analizi yapılır ve kanıtlanmamış bir değişkenin değerinin okunmasına izin verilmez. Yani hala değişkenden okuyamıyorsunuz.
Diyelim ki try bloğundan sonra değişkenden okumaya çalışıyorum:
Console.WriteLine(firstVariable);
Yani verecek bir derleme zamanı hatası :
CS0165 Atanmamış yerel değişkenin 'firstVariable' kullanımı
Aradım Environment.Exit böylece, catch bloğunda ben değişken öncesinde Console.WriteLine çağrıya atanmıştır biliyoruz. Ancak derleyici bu çıkarım yapmaz.
Derleyici neden bu kadar katı?
Bunu bile yapamam:
int n;
try {
n = 10; // I know this won't throw an IOException.
}
catch (IOException) {
}
Console.WriteLine(n);
Bu kısıtlamaya bakmanın bir yolu, C # 'daki kesin atama analizinin çok karmaşık olmadığını söylemektir. Ancak buna bakmanın bir başka yolu, catch cümlecikleri olan bir try bloğuna kod yazdığınızda, hem derleyiciye hem de herhangi bir insan okuyucusuna, hepsinin çalışamayacağı gibi davranılması gerektiğini söylemenizdir.
Ne demek istediğimi göstermek için, derleyicinin yukarıdaki koda izin verip vermediğini düşünün, ancak daha sonra kişisel olarak bildiğiniz bir istisna atmayacak bir işleve try bloğuna bir çağrı eklediniz . Çağrılan işlevin bir atamadığını garanti edememek IOException
, derleyici n
atandığını bilmiyordu ve sonra yeniden ateşlemeniz gerekecekti.
Diğer bir deyişle, catch cümleciklerine sahip bir try bloğuna atanmış bir değişkenin kesinlikle sonradan atanıp atanmadığının belirlenmesinde oldukça karmaşık bir analiz yapılmasından önce derleyici, daha sonra kırılması muhtemel kod yazmaktan kaçınmanıza yardımcı olur. (Sonuçta, bir istisna yakalamak, genellikle birinin atılabileceğini düşündüğünüz anlamına gelir.)
Değişkenin tüm kod yollarından atandığından emin olabilirsiniz.
Kod derlemesini değişkene try bloğundan önce veya catch bloğunda bir değer vererek derleyebilirsiniz. Bu şekilde, try bloğundaki atama gerçekleşmese bile, yine de başlatılmış veya atanmış olacaktır. Örneğin:
var n = 0; // But is this meaningful, or just covering a bug?
try {
n = 10;
}
catch (IOException) {
}
Console.WriteLine(n);
Veya:
int n;
try {
n = 10;
}
catch (IOException) {
n = 0; // But is this meaningful, or just covering a bug?
}
Console.WriteLine(n);
Bu derler. Ama varsayılan değer bunu mantıklı vermek keşke böyle bir şey yapmak en iyisidir * ve doğru davranış üretir.
Değişkeni try bloğunda ve tüm catch bloklarında atadığınız bu ikinci durumda, try-catch'ten sonra değişkeni okuyabilseniz de, değişkeni ekli bir finally
bloğun içinde okuyamazsınız , çünkü İnfaz, düşündüğümüzden daha fazla durumda bir deneme bloğu bırakabilir .
* Bu arada, C ve C ++ gibi bazı dillerin her ikisi de başlatılmamış değişkenlere izin verir ve bunlardan okumayı engellemek için kesin atama çözümlemelerine sahip değildir . Başlatılmamış belleğin okunması, programların nitel olmayan ve düzensiz bir şekilde davranmasına neden olduğundan , genellikle bir başlatıcı sağlamadan bu dillerde değişkenleri eklemekten kaçınılması önerilir . C # ve Java gibi kesin atama analizine sahip dillerde, derleyici, başlatılmamış değişkenleri okumaktan ve ayrıca daha sonra anlamlı olarak yanlış yorumlanabilecek anlamsız değerlerle başlatılmasından daha az kötülükten sizi kurtarır.
Değişken atanmamış kod yollarını bir istisna (veya geri dönüş) atayarak yapabilirsiniz.
Bazı eylemler gerçekleştirmeyi (günlük tutma gibi) planlıyorsanız ve istisnayı yeniden başlatırsanız veya başka bir istisna atarsanız ve bu, değişkenin atanmamış olduğu herhangi bir catch cümlesinde gerçekleşirse, derleyici değişkenin atandığını bilir:
int n;
try {
n = 10;
}
catch (IOException e) {
Console.Error.WriteLine(e.Message);
throw;
}
Console.WriteLine(n);
Bu derler ve makul bir seçim olabilir. İstisna sadece atılmış olmadıkça Ancak, fiili uygulamalarda, hatta kurtarmayı denemek için bir anlam ifade etmiyor durumlar * , hala alıcı emin olmalı ve düzgün işleme yere .
(Bu durumda bir nihayet bloğundaki değişkeni de okuyamazsınız, ancak bunu yapmanız gerektiği gibi hissetmezsiniz - sonuçta, nihayetinde esasen her zaman çalışan bloklar ve bu durumda değişken her zaman atanmaz .)
* Örneğin, çoğu uygulama OutOfMemoryException'ı işleyen bir yakalama yan tümcesine sahip değildir, çünkü bu konuda yapabilecekleri herhangi bir şey en azından çarpma kadar kötü olabilir .
Belki gerçekten do kod refactor istiyor.
Örneğinizde, tanıtmak firstVariable
ve secondVariable
denemek blokları. Söylediğim gibi, onları atandıkları deneme bloklarından önce tanımlayabilir, böylece daha sonra kapsam dahilinde kalabilir ve derleyiciyi her zaman atandıklarından emin olarak onlardan okumanızı sağlayacak şekilde kandırabilir / kandırabilirsiniz.
Ancak bu bloklardan sonra görünen kod muhtemelen doğru şekilde atanmasına bağlıdır. Bu durumda, kodunuz bunu yansıtmalı ve sağlamalıdır.
Öncelikle, oradaki hatayı gerçekten halledebilir misiniz (ve gerekir)? İstisna işlemenin ortaya çıkmasının nedenlerinden biri, oluştukları yere yakın olmasa bile, etkili bir şekilde ele alınabilecek hataları ele almayı kolaylaştırmaktır .
Baştaki ve bu değişkenleri kullanan işlevdeki hatayı gerçekten işleyemiyorsanız, belki try bloğu o işlevde olmamalı, bunun yerine daha yüksek bir yerde (örneğin, bu işlevi çağıran kodda veya kodda) olmalıdır. çağrıları olduğunu o kodu). Yanlışlıkla başka bir yere atılan bir istisnayı yakalamadığınızdan ve yanlış bir şekilde başlatılırken firstVariable
ve a secondVariable
.
Başka bir yaklaşım, değişkenleri kullanan kodu try bloğuna koymaktır. Bu genellikle makuldür. Yine, başlatıcılarından yakaladığınız istisnalar dışında kalan kodlardan da atılabilirse, bunları kullanırken bu olasılığı ihmal etmediğinizden emin olmalısınız.
(Örneklerinizde gösterilenden daha karmaşık ifadelerle değişkenleri başlattığınızı, aslında bir istisna atabileceklerini ve ayrıca tüm olası istisnaları yakalamak için değil , sadece özel istisnaları yakalamak için planladığınızı farz ediyorum. yapabilirsin Öngörülebilir ve anlamlı bir şekilde başa Gerçek dünyanın her zaman bu kadar iyi olmadığı ve üretim kodunun bazen bunu yaptığı doğrudur , ancak buradaki amacınız iki belirli değişkeni başlatırken meydana gelen hataları ele almak olduğu için, bu belirli için yazdığınız herhangi bir catch cümlesi amaç ne olursa olsun her türlü hataya özel olmalıdır.)
Üçüncü bir yol başarısız olabilecek kodu ve onu kullanan try-catch'i kendi yöntemine çıkarmaktır . Bu, önce tamamen hatalarla baş etmek isteyip istemediğiniz ve daha sonra başka bir yerde ele alınması gereken bir istisnayı istemeden yakalamaktan endişe duymamanız durumunda faydalıdır.
Örneğin, herhangi bir değişkeni atayamazsanız uygulamadan hemen çıkmak istediğinizi varsayalım. (Açıkçası, tüm istisnaların ele alınması ölümcül hatalar için değildir; bu sadece bir örnektir ve uygulamanızın soruna nasıl tepki vermesini istediğinizi olabilir veya olmayabilir.) Buna benzer bir şey yapabilirsiniz:
// In real life, this should be named more descriptively.
private static (int firstValue, int secondValue) GetFirstAndSecondValues()
{
try {
// This code is contrived. The idea here is that obtaining the values
// could actually fail, and throw a SomeSpecificException.
var firstVariable = 1;
var secondVariable = firstVariable;
return (firstVariable, secondVariable);
}
catch (SomeSpecificException e) {
Console.Error.WriteLine(e.Message);
Environment.Exit(1);
throw new InvalidOperationException(); // unreachable
}
}
// ...and of course so should this.
internal static void MethodThatUsesTheValues()
{
var (firstVariable, secondVariable) = GetFirstAndSecondValues();
// Code that does something with them...
}
Bu kod , birden fazla değer döndürmek için C # 7.0'ın sözdizimine sahip bir ValueTuple döndürür ve yapıyı kaldırır , ancak hala daha önceki bir C # sürümündeyseniz, bu tekniği kullanabilirsiniz; örneğin, parametreleri kullanabilir veya her iki değeri de sağlayan özel bir nesne döndürebilirsiniz. . Ayrıca, eğer iki değişken aslında sıkı bir şekilde ilişkili değilse, yine de iki ayrı yönteme sahip olmak muhtemelen daha iyi olacaktır.
Özellikle bunun gibi birden fazla yönteminiz varsa, kullanıcıya ölümcül hataları bildirme ve bırakma konusunda kodunuzu merkezileştirmelisiniz. (Örneğin, parametresi Die
olan bir yöntem yazabilirsiniz message
.)throw new InvalidOperationException();
Çizgi aslında hiçbir zaman yürütülür değil ihtiyacınız kadar (ve olmamalıdır) bunun için bir yakalama maddesi yazın.
Belirli bir hata oluştuğunda bırakmanın dışında , orijinal istisnayı saran başka bir tür istisna atarsanız, bazen buna benzer bir kod yazabilirsiniz . (Bu durumda, sen değil ikinci, ulaşılamaz atış ifadesi gerekir.)
Sonuç: Kapsam resmin sadece bir kısmıdır.
Kodunuzu yeniden işleme koymadan hata işleme ile (veya tercih ederseniz, neredeyse hiç yeniden işleme koymadan ), yalnızca değişkenlerin bildirimlerini atadıklarından ayırarak sarma işlemini gerçekleştirebilirsiniz . Derleyici, eğer C # 'nın belirli atama kurallarını yerine getirirseniz ve try bloğunun önünde bir değişken olduğunu bildirirseniz daha geniş kapsamı açık hale gelir. Ancak daha fazla refactoring hala en iyi seçenek olabilir.
try.. catch
, belirli bir kod bloğu türüdür ve tüm kod blokları gittiği sürece, bir değişkeni tanımlayamaz ve aynı değişkeni başka bir alanda kapsam olarak kullanamazsınız.