C # 'da boş parametre denetimi


88

C # 'da, null değerinin geçerli bir değer olmadığı her işleve parametre null kontrolleri eklemek için herhangi bir iyi neden (daha iyi bir hata mesajı dışında) var mı? Açıkçası, s kullanan kod yine de bir istisna atacaktır. Ve bu tür kontroller kodu yavaşlatır ve bakımını zorlaştırır.

void f(SomeType s)
{
  if (s == null)
  {
    throw new ArgumentNullException("s cannot be null.");
  }

  // Use s
}

13
Basit bir boş kontrolün kodunuzu önemli (hatta ölçülebilir) bir miktarda yavaşlatacağından ciddi şüpheliyim.
Heinzi

"S" nin ne yaptığına bağlı. Tüm yaptığı "s.SomeField döndürmek" ise, fazladan kontrol muhtemelen yöntemi bir büyüklük sırasına göre yavaşlatacaktır.
kaalus

11
@kaalus: Muhtemelen? Muhtemelen kitabıma uymuyor. Test verileriniz nerede? Ve bu tür bir değişikliğin ilk etapta uygulamanızın darboğazını oluşturması ne kadar olasıdır?
Jon Skeet

@Jon: haklısın, burada sağlam verilerim yok. JIT satırının bu ekstra "eğer" tarafından etkileneceği Windows Phone veya Xbox için geliştirirseniz ve dallanmanın çok pahalı olduğu yerlerde, oldukça paranoyak olabilirsiniz.
kaalus

3
@kaalus: Öyleyse, bu çok özel durumlarda kod stilinizi önemli bir fark yarattığını kanıtladıktan sonra ayarlayın veya Debug.Assert'i kullanın (yine, yalnızca bu durumlarda, Eric'in cevabına bakın), böylece kontroller yalnızca belirli sürümlerde açık olur.
Jon Skeet

Yanıtlar:


167

Evet, iyi sebepler var:

  • Neyin null olduğunu tam olarak tanımlar, ki bu bir NullReferenceException
  • Başka bir koşul değerin referans alınmadığı anlamına gelse bile, kodun geçersiz girişte başarısız olmasına neden olur.
  • Yöntemin ilk başvurudan önce ulaşabileceğiniz başka yan etkilere sahip olmasından önce istisnayı meydana getirir.
  • Bu, parametreyi başka bir şeye aktarırsanız, onların sözleşmesini ihlal etmediğinizden emin olabileceğiniz anlamına gelir.
  • Yönteminizin gereksinimlerini belgeler ( Kod Sözleşmelerini kullanmak elbette bunun için daha iyidir)

Şimdi itirazlarınıza gelince:

  • Bu yavaş var : Var Eğer buldum aslında Kodunuzdaki darboğaz olur, yoksa tahmin edilmektedir? Hükümsüzlük kontroller çok hızlı, ve vakaların büyük çoğunluğunda onlar konum değil darboğaz olacak
  • Kodun korunmasını zorlaştırıyor : Bence tam tersi. Bence, bir parametrenin boş olup olmadığına ve bu koşulun uygulandığından emin olduğunuzda açıkça anlaşıldığı yerlerde kodu kullanmak daha kolaydır .

Ve iddianız için:

Açıkçası, s kullanan kod yine de bir istisna atacaktır.

Gerçekten mi? Düşünmek:

void f(SomeType s)
{
  // Use s
  Console.WriteLine("I've got a message of {0}", s);
}

Bu kullanır s, ancak bir istisna oluşturmaz. İçin geçersiz ise sboş olması ve bu bir şeyler olduğu anlamına yanlış gösterir, bir istisna burada en uygun davranıştır.

Şimdi bu argüman doğrulama kontrollerini nereye koyduğunuz farklı bir konudur. Kendi sınıfınızdaki tüm koda güvenmeye karar verebilirsiniz, böylece özel yöntemlerle uğraşmayın. Toplantınızın geri kalanına güvenmeye karar verebilirsiniz, bu yüzden dahili yöntemlerle uğraşmayın. Herkese açık yöntemler için argümanları neredeyse kesinlikle doğrulamalısınız.

Bir yan not: tek parametreli oluşturucu aşırı yüklemesi ArgumentNullExceptionyalnızca parametre adı olmalıdır, bu nedenle testiniz şöyle olmalıdır:

if (s == null)
{
  throw new ArgumentNullException("s");
}

Alternatif olarak, biraz terser'e izin veren bir genişletme yöntemi oluşturabilirsiniz:

s.ThrowIfNull("s");

Benim (jenerik) uzantı yöntemi sürümümde, boş değilse orijinal değeri döndürmesini sağlıyorum, aşağıdaki gibi şeyler yazmanıza izin veriyor:

this.name = name.ThrowIfNull("name");

Ayrıca, bu konuda çok endişelenmiyorsanız, parametre adını almayan bir aşırı yüklemeye de sahip olabilirsiniz.


8
Uzantı yöntemi için +1. Ben gibi diğer doğrulama dahil aynı şeyi yaptık ThrowIfEmptyüzerindeICollection
Davy8

5
Neden sürdürmenin daha zor olduğunu düşünüyorum: Her seferinde programcı müdahalesini gerektiren her manuel mekanizmada olduğu gibi, bu, hatalara ve ihmallere eğilimlidir ve kodu karıştırır. Kesintili güvenlik, hiç olmamasından daha kötüdür.
kaalus

8
@kaalus: Testlere de aynı tutumu uyguluyor musunuz? "Testlerim olası tüm hataları yakalayamayacak, bu yüzden hiç yazmayacağım" Güvenlik mekanizmaları işe yaradığında, sorunu bulmayı kolaylaştıracak ve başka yerlerde etkiyi azaltacaktır (sorunu daha erken yakalayarak). Bu ... bir 10 üzerinden 0 kez oluyor daha hala daha iyi 10 üzerinden 9 kez olursa
Jon Skeet

2
@DoctorOreo: Kullanmıyorum Debug.Assert. Üretimdeki hataları (gerçek verileri mahvetmeden önce) yakalamak, geliştirmeden daha önemlidir.
Jon Skeet

3
Şimdi C # 6.0 ile kullanabiliriz throw new ArgumentNullException(nameof(s))
hrzafer

52

Jon'a katılıyorum ama buna bir şey daha ekleyeceğim.

Açıkça boş kontrollerin ne zaman ekleneceğine ilişkin tutumum şu öncüllere dayanmaktadır:

  • Birim testlerinizin bir programdaki her ifadeyi kullanması için bir yol olmalıdır.
  • throwifadelerdir ifadeler .
  • ifBir ifadenin sonucu bir ifadedir .
  • Bu nedenle, egzersiz için bir yol olmalı throwinif (x == null) throw whatever;

Bu ifadenin yürütülebilmesi için olası bir yol yoksa, test edilemez ve ile değiştirilmelidir Debug.Assert(x != null);.

Bu ifadenin yürütülmesi için olası bir yol varsa, ifadeyi yazın ve ardından onu uygulayan bir birim testi yazın.

Öyle özellikle kamu türlerinin kamu yöntemleri bu şekilde onların argümanları kontrol önemlidir; kullanıcılarınızın ne kadar çılgınca bir şey yapacağı hakkında hiçbir fikriniz yok. Onlara "hey seni mankafa, yanlış yapıyorsun!" mümkün olan en kısa sürede istisna.

Özel türlerin özel yöntemleri, tersine, argümanları kontrol ettiğiniz ve argümanın hiçbir zaman boş olmadığına dair güçlü bir garantiye sahip olduğunuz durumda olma olasılığı çok daha yüksektir; bu değişmezi belgelemek için bir iddia kullanın.


23

Bunu bir yıldır kullanıyorum:

_ = s ?? throw new ArgumentNullException(nameof(s));

Bu bir oneliner ve discard ( _) gereksiz tahsis olmadığı anlamına gelir.


8

Açık olmadan ifçek, o anlamaya çok zor olabilir neyi idi nullkodu sahip değilseniz.

Eğer bir alırsanız NullReferenceExceptionkaynak kodu olmadan bir kütüphane içinde derin, yanlış yaptığını endam bir sürü sorun olması muhtemel.

Bu ifkontroller, kodunuzu fark edilir ölçüde yavaşlatmaz.


Parametre unutmayın ArgumentNullExceptionyapıcı bir parametre adı değil, bir mesajdır.
Kodunuz şöyle olmalıdır

if (s == null) throw new ArgumentNullException("s");

Bunu kolaylaştırmak için bir kod parçası yazdım:

<?xml version="1.0" encoding="utf-8" ?>
<CodeSnippets  xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet">
    <CodeSnippet Format="1.0.0">
        <Header>
            <Title>Check for null arguments</Title>
            <Shortcut>tna</Shortcut>
            <Description>Code snippet for throw new ArgumentNullException</Description>
            <Author>SLaks</Author>
            <SnippetTypes>
                <SnippetType>Expansion</SnippetType>
                <SnippetType>SurroundsWith</SnippetType>
            </SnippetTypes>
        </Header>
        <Snippet>
            <Declarations>
                <Literal>
                    <ID>Parameter</ID>
                    <ToolTip>Paremeter to check for null</ToolTip>
                    <Default>value</Default>
                </Literal>
            </Declarations>
            <Code Language="csharp"><![CDATA[if ($Parameter$ == null) throw new ArgumentNullException("$Parameter$");
        $end$]]>
            </Code>
        </Snippet>
    </CodeSnippet>
</CodeSnippets>

5

Parametre olarak boş nesneler almadığınızdan emin olmak için daha hoş bir yola ihtiyacınız varsa , Kod Sözleşmelerine bir göz atmak isteyebilirsiniz .


2

Ana faydası, yönteminizin gereksinimlerini en başından itibaren açıkça belirtmenizdir. Bu, kod üzerinde çalışan diğer geliştiricilere, arayanın yönteminize boş bir değer göndermesinin gerçekten bir hata olduğunu açıkça ortaya koyar.

Kontrol ayrıca, başka herhangi bir kod yürütülmeden önce yöntemin yürütülmesini durduracaktır. Bu, tamamlanmamış kalan yöntemle yapılan değişiklikler konusunda endişelenmenize gerek olmadığı anlamına gelir.


2

Bu istisnaya ulaştığınızda bazı hata ayıklamalardan tasarruf sağlar.

ArgumentNullException açıkça bunun boş olan "s" olduğunu belirtir.

Bu denetime sahip değilseniz ve kodun patlamasına izin verdiyseniz, bu yöntemdeki bazı tanımlanmamış satırlardan bir NullReferenceException alırsınız. Bir sürüm yapısında satır numaralarını almazsınız!


0

Orijinal kod:

void f(SomeType s)
{
  if (s == null)
  {
    throw new ArgumentNullException("s cannot be null.");
  }

  // Use s
}

Şu şekilde yeniden yazın:

void f(SomeType s)
{
  if (s == null) throw new ArgumentNullException(nameof(s));
}

Kullanarak yeniden yazmanın nedeni, nameofdaha kolay yeniden düzenlemeye izin vermesidir. Değişkeninizin adı sherhangi bir şekilde değişirse, o zaman hata ayıklama mesajları da güncellenecektir, oysa değişkenin adını sadece sabit kodlarsanız, zaman içinde güncellemeler yapıldığında sonunda güncelliğini yitirecektir. Endüstride kullanılan iyi bir uygulamadır.


-2
int i = Age ?? 0;

Yani örneğiniz için:

if (age == null || age == 0)

Veya:

if (age.GetValueOrDefault(0) == 0)

Veya:

if ((age ?? 0) == 0)

Veya üçlü:

int i = age.HasValue ? age.Value : 0;
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.