C #에서 Null 매개 변수 검사


85

C #에서 null이 유효한 값이 아닌 모든 함수에 매개 변수 null 검사를 추가하는 데 좋은 이유 (더 나은 오류 메시지 제외)가 있습니까? 분명히 s를 사용하는 코드는 어쨌든 예외를 던질 것입니다. 그리고 이러한 검사는 코드를 유지 관리하기 더 느리고 어렵게 만듭니다.

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

  // Use s
}

12
간단한 null 검사가 코드 속도를 상당히 (또는 측정 가능한) 정도로 느리게 할 것이라고 진지하게 의심합니다.
Heinzi 2011 년

글쎄, 그것은 "Use s"가하는 일에 달려 있습니다. "return s.SomeField"만 수행하면 추가 검사로 인해 메서드 속도가 몇 배 느려질 수 있습니다.
kaalus 2011 년

10
@kaalus : 아마? 아마도 내 책에서 잘라 내지 않을 것입니다. 테스트 데이터는 어디에 있습니까? 그리고 그러한 변경이 애초에 애플리케이션의 병목 현상이 될 가능성은 얼마나됩니까?
Jon Skeet 2011 년

@Jon : 당신 말이 맞아요. 여기에는 하드 데이터가 없습니다. JIT 인라인이이 추가 "if"의 영향을받는 Windows Phone 또는 Xbox 용으로 개발하고 분기 비용이 매우 비싸면 편집증에 빠질 수 있습니다.
kaalus 2011 년

3
@kaalus : 따라서 상당한 차이가 있음을 입증 한 후 매우 구체적인 경우 코드 스타일을 조정 하거나 Debug.Assert를 사용하십시오 (다시 해당 상황에서만 Eric의 답변 참조).
Jon Skeet 2011 년

답변:


163

예, 그럴만 한 이유가 있습니다.

  • null이 무엇인지 정확히 식별합니다. NullReferenceException
  • 다른 조건이 값이 역 참조되지 않음을 의미하더라도 잘못된 입력에서 코드가 실패하게 만듭니다.
  • 메서드가 첫 번째 역 참조 전에 도달 할 수있는 다른 부작용을 갖기 전에 예외가 발생 하도록합니다.
  • 그것은 당신이 뭔가에 매개 변수를 전달하면 것을 확신 할 수 있다는 것을 의미합니다, 당신은 위반하지 않는 자신의 계약을
  • 그것은 당신의 방법의 요구 사항을 문서화합니다 ( 물론 코드 계약을 사용하는 것이 더 좋습니다)

이제 귀하의 이의 제기 :

  • 더 느립니다 : 이것이 실제로 코드의 병목 현상임을 발견 했습니까 , 아니면 추측하고 있습니까? Nullity 검사는 매우 빠르며 대부분의 경우 병목 현상이 발생 하지 않습니다.
  • 코드를 유지하기가 더 어려워집니다 . 그 반대라고 생각합니다. 매개 변수가 null 일 수 있는지 여부와 해당 조건이 적용된다는 확신이있는 경우 코드를 사용하는 것이 더 쉽다고 생각합니다 .

그리고 당신의 주장 :

분명히 s를 사용하는 코드는 어쨌든 예외를 던질 것입니다.

정말? 중히 여기다:

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

를 사용 s하지만 예외는 발생하지 않습니다. snull이되는 것이 유효하지 않고 문제가 있음을 나타내는 경우 여기에서 예외가 가장 적절한 동작입니다.

이제 인수 유효성 검사를 어디에 두는 것은 다른 문제입니다. 자신의 클래스 내의 모든 코드를 신뢰하기로 결정할 수 있으므로 개인 메서드에 신경 쓰지 마십시오. 나머지 어셈블리를 신뢰하기로 결정할 수 있으므로 내부 메서드에 신경 쓰지 마십시오. 공용 메서드에 대한 인수의 유효성을 거의 확실하게 확인해야합니다.

참고 :의 단일 매개 변수 생성자 오버로드 ArgumentNullException는 매개 변수 이름이어야하므로 테스트는 다음과 같아야합니다.

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

또는 확장 메소드를 생성하여 다소 간결하게 할 수 있습니다.

s.ThrowIfNull("s");

내 버전의 (일반) 확장 메서드에서는 null이 아닌 경우 원래 값을 반환하여 다음과 같은 내용을 작성할 수 있습니다.

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

너무 신경 쓰지 않으면 매개 변수 이름을 사용하지 않는 오버로드를 가질 수도 있습니다.


8
확장 방법은 +1입니다. 같은 다른 유효성 검사를 포함하여 똑같은 작업을 수행 ThrowIfEmpty했습니다ICollection
Davy8

5
유지하기가 더 어렵다고 생각하는 이유 : 매번 프로그래머의 개입이 필요한 모든 수동 메커니즘과 마찬가지로 이것은 실수와 누락이 생기고 코드를 복잡하게 만듭니다. 불안정한 안전은 전혀 안전하지 않은 것보다 더 나쁩니다.
kaalus

8
@kaalus : 테스트에 동일한 태도를 적용합니까? "내 테스트는 가능한 모든 버그를 잡아 내지 못하므로 어떤 버그도 작성하지 않을 것입니다"? 안전 메커니즘 작동하면 문제를 더 쉽게 찾고 다른 곳에서 영향을 줄일 수 있습니다 (문제를 더 일찍 파악하여). 그 ... 그것은 10에서 0 번 일어나는 것보다 여전히 더 나은 10 명 중 9 번 발생하면
존 소총

2
@DoctorOreo : 저는 Debug.Assert. 프로덕션에서 오류가 발생하기 전에 (실제 데이터가 망가지기 전에) 개발에서보다 오류를 포착하는 것이 훨씬 더 중요합니다.
Jon Skeet

2
이제 C # 6.0으로 우리는 사용할 수 있습니다 throw new ArgumentNullException(nameof(s))
hrzafer

52

나는 Jon의 의견에 동의하지만 여기에 한 가지 추가하겠습니다.

명시 적 null 검사를 추가 할 때에 대한 나의 태도는 다음 전제를 기반으로합니다.

  • 단위 테스트가 프로그램의 모든 명령문을 실행하는 방법이 있어야합니다.
  • throw진술은 진술 입니다.
  • 의 결과 if진술 입니다.
  • 따라서 운동 할 수있는 방법이 있어야한다 throw의를if (x == null) throw whatever;

해당 명령문을 실행할 수있는 방법없으면 테스트 할 수 없으며 Debug.Assert(x != null);.

해당 명령문을 실행할 수있는 방법이 있으면 명령문을 작성한 다음이를 실행하는 단위 테스트를 작성하십시오.

퍼블릭 타입의 퍼블릭 메서드는 이러한 방식으로 인수를 확인하는 것이 특히 중요합니다. 사용자가 어떤 미친 짓을할지 전혀 모릅니다. 그들에게 "이봐, 멍청 아, 잘못하고있어!" 가능한 한 빨리 예외.

대조적으로 private 형식의 private 메서드는 인수를 제어하는 ​​상황에있을 가능성이 훨씬 더 높으며 인수가 null이 아님을 강력하게 보장 할 수 있습니다. 그 불변성을 문서화하기 위해 어설 션을 사용하십시오.


22

나는 이것을 1 년 동안 사용하고있다.

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

oneliner이고, 폐기 ( _)는 불필요한 할당이 없음을 의미합니다.


8

명시 적없이 if검사, 파악하기가 매우 어려울 수 있습니다 무엇을 했다 null코드를 소유하지 않은 경우.

NullReferenceException소스 코드가없는 라이브러리 깊은 곳에서 정보 를 얻는다면 , 무엇을 잘못했는지 파악하는 데 많은 어려움이있을 것입니다.

이러한 if검사로 인해 코드가 눈에 띄게 느려지지는 않습니다.


받는 매개 변수 참고 ArgumentNullException생성자는 매개 변수 이름이 아닌 메시지입니다.
귀하의 코드는

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

이 작업을 더 쉽게하기 위해 코드 조각을 작성했습니다.

<?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

null 개체를 매개 변수로 가져 오지 않도록 더 좋은 방법이 필요한 경우 코드 계약을 살펴볼 수 있습니다 .


2

가장 큰 이점은 처음부터 방법의 요구 사항을 명확하게 할 수 있다는 것입니다. 이렇게하면 코드를 작업하는 다른 개발자에게 호출자가 메서드에 null 값을 보내는 것이 실제로 오류임을 분명히 알 수 있습니다.

이 검사는 다른 코드가 실행되기 전에 메서드 실행도 중지합니다. 즉, 미완성으로 남겨진 방법에 의해 수정되는 것에 대해 걱정할 필요가 없습니다.


2

해당 예외가 발생하면 일부 디버깅이 절약됩니다.

ArgumentNullException은 null 인 "s"임을 명시 적으로 나타냅니다.

해당 검사가없고 코드가 작동하도록하면 해당 메서드의 식별되지 않은 줄에서 NullReferenceException이 발생합니다. 릴리스 빌드에서는 줄 번호가 없습니다!


0

원래 코드 :

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

  // Use s
}

다음과 같이 다시 작성하십시오.

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

[편집] 사용하여 다시 작성하는 이유 nameof는 쉽게 리팩토링 할 수 있기 때문입니다. 변수 이름이 s변경되면 디버깅 메시지도 업데이트되는 반면 변수 이름을 하드 코딩하면 시간이 지남에 따라 업데이트 될 때 결국 구식이됩니다. 업계에서 사용되는 좋은 관행입니다.


변경을 제안하는 경우 이유를 설명하십시오. 또한 질문에 답하고 있는지 확인하십시오.
CodeCaster 2017

-1
int i = Age ?? 0;

따라서 귀하의 예를 들어 :

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

또는:

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

또는:

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

또는 삼항 :

int i = age.HasValue ? age.Value : 0;
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.