C #에서 null이 아닌 경우 메서드 호출


106

어떻게 든이 진술을 단축 할 수 있습니까?

if (obj != null)
    obj.SomeMethod();

이 글을 많이 써서 꽤 짜증이 나기 때문입니다. 내가 생각할 수있는 유일한 방법은 Null Object 패턴 을 구현하는 것입니다 .하지만 그것은 매번 할 수있는 일이 아니며 구문을 단축하는 해결책도 아닙니다.

그리고 이벤트와 유사한 문제는

public event Func<string> MyEvent;

그런 다음 호출

if (MyEvent != null)
    MyEvent.Invoke();

41
C # 4에 새 연산자를 추가하는 것을 고려했습니다. "obj.?SomeMethod ()"는 "obj가 null이 아니면 SomeMethod를 호출하고 그렇지 않으면 null을 반환합니다"를 의미합니다. 안타깝게도 예산에 맞지 않아 구현하지 않았습니다.
— Eric Lippert

@Eric :이 의견이 여전히 유효합니까? 나는 어딘가에서 보았습니다. 4.0에서 사용할 수 있습니까?
— CharithJ

@CharithJ : 아니요. 구현되지 않았습니다.
— Eric Lippert

3
@CharithJ : 널 병합 연산자의 존재를 알고 있습니다. 그것은 Darth가 원하는 것을하지 않습니다. 그는 Null이 가능한 멤버 액세스 연산자를 원합니다. (그런데 이전 의견은 null 병합 연산자의 잘못된 특성을 제공합니다. "v = m == null? y : m. 값은 v = m ?? y"로 쓸 수 있습니다.)
— Eric Lippert

4
최신 독자의 경우 : C # 6.0은?.를 구현하므로 x? .y? .z? .ToString ()은 x, y 또는 z가 null이면 null을 반환하고, 둘 다 null이 아닌 경우 z.ToString ()을 반환합니다.
— David

답변:


162

C # 6부터는 다음을 사용할 수 있습니다.

MyEvent?.Invoke();

또는:

obj?.SomeMethod();

는 ?.널 전파 연산자이며,이 발생할 .Invoke()오퍼랜드 인 경우 단락 될 null. 피연산자는 한 번만 액세스되므로 "확인 및 호출 사이의 값 변경"문제의 위험이 없습니다.

===

C # 6 이전에는 아니요 : 한 가지 예외를 제외하고는 null-safe 마법이 없습니다. 확장 방법-예 :

public static void SafeInvoke(this Action action) {
    if(action != null) action();
}

이제 이것은 유효합니다.

Action act = null;
act.SafeInvoke(); // does nothing
act = delegate {Console.WriteLine("hi");}
act.SafeInvoke(); // writes "hi"

이벤트의 경우 경쟁 조건을 제거 할 수 있다는 장점이 있습니다. 즉, 임시 변수가 필요하지 않습니다. 따라서 일반적으로 다음이 필요합니다.

var handler = SomeEvent;
if(handler != null) handler(this, EventArgs.Empty);

하지만 함께:

public static void SafeInvoke(this EventHandler handler, object sender) {
    if(handler != null) handler(sender, EventArgs.Empty);
}

간단하게 사용할 수 있습니다.

SomeEvent.SafeInvoke(this); // no race condition, no null risk

1
'이것에 대해 약간의 갈등이 있습니다. 일반적으로 더 독립적 인 작업 또는 이벤트 처리기의 경우 이는 의미가 있습니다. 그래도 일반 메서드에 대한 캡슐화를 중단하지는 않습니다. 이것은 별도의 정적 클래스에서 메서드를 생성하는 것을 의미하며 캡슐화를 잃고 전체적으로 가독성 / 코드 구성을 저하시키는 것이 로컬 가독성을 약간 개선 할 가치가 있다고 생각하지 않습니다
— tvanfosson

@tvanfosson-참으로; 그러나 내 요점은 이것이 어디에서 작동할지 아는 유일한 경우라는 것입니다. 그리고 질문 자체가 대표자 / 행사의 주제를 제기합니다.
— Marc Gravell

이 코드는 어딘가에 익명 메서드를 생성하여 예외의 스택 추적을 실제로 엉망으로 만듭니다. 익명의 메소드 이름을 지정할 수 있습니까? ;)
— sisve

2015 / C # 6.0에 따라 잘못됨 ...? 그는 soltion입니다. ReferenceTHatMayBeNull? .CallMethod ()는 null 일 때 메서드를 호출하지 않습니다.
— TomTom

1
@mercu 있어야합니다 ?.-VB14 이상
— Marc Gravell

27

당신이 찾고있는 것은 Null Conditional ( "coalescing"아님) 연산자 : ?.. C # 6부터 사용할 수 있습니다.

귀하의 예는 obj?.SomeMethod();. obj가 null이면 아무 일도 일어나지 않습니다. 메소드에 인수가있는 경우, 예를 들어 obj?.SomeMethod(new Foo(), GetBar());인수 obj가 null이면 평가되지 않습니다 . 인수 평가에 부작용이 있는지 여부가 중요합니다.

그리고 연결이 가능합니다. myObject?.Items?[0]?.DoSomething()


1
환상적이다. 이것이 C # 6 기능이라는 점에 주목할 가치가 있습니다 ... (VS2015 문에서 암시되지만 여전히 주목할 가치가 있습니다) :)
— Kyle Goode

10

빠른 확장 방법 :

    public static void IfNotNull<T>(this T obj, Action<T> action, Action actionIfNull = null) where T : class {
        if(obj != null) {
            action(obj);
        } else if ( actionIfNull != null ) {
            actionIfNull();
        }
    }

예:

  string str = null;
  str.IfNotNull(s => Console.Write(s.Length));
  str.IfNotNull(s => Console.Write(s.Length), () => Console.Write("null"));

또는 대안으로 :

    public static TR IfNotNull<T, TR>(this T obj, Func<T, TR> func, Func<TR> ifNull = null) where T : class {
        return obj != null ? func(obj) : (ifNull != null ? ifNull() : default(TR));
    }

예:

    string str = null;
    Console.Write(str.IfNotNull(s => s.Length.ToString());
    Console.Write(str.IfNotNull(s => s.Length.ToString(), () =>  "null"));

나는 확장 메소드로 그것을 시도했고 거의 동일한 코드로 끝났습니다. 그러나이 구현의 문제점은 값 유형을 반환하는 표현식에서는 작동하지 않는다는 것입니다. 그래서 두 번째 방법이 필요했습니다.
— orad 2014-07-25

4

이벤트는 제거되지 않는 빈 기본 델리게이트로 초기화 할 수 있습니다.

public event EventHandler MyEvent = delegate { };

널 검사가 필요하지 않습니다.

[ 업데이트 ,이를 지적 해준 Bevan에게 감사드립니다]

그러나 가능한 성능 영향을 인식하십시오. 내가 한 빠른 마이크로 벤치 마크에 따르면 "기본 델리게이트"패턴을 사용할 때 구독자가없는 이벤트를 처리하는 것이 2-3 배 느립니다. (저의 듀얼 코어 2.5GHz 노트북에서는 279ms : 5 천만 개의 미가입 이벤트를 발생시키는 데 785ms를 의미합니다.) 애플리케이션 핫스팟의 경우 고려해야 할 문제가 될 수 있습니다.


1
그래서, 당신은 빈 델리게이트를 호출함으로써 null-check를 피할 수 있습니다 ... 몇 번의 키 입력을 절약하기 위해 메모리와 시간을 측정 할 수있는 희생? YMMV이지만 나에게는 좋지 않은 트레이드 오프입니다.
— Bevan

또한 여러 델리게이트가 하나의 이벤트에 등록 된 경우보다 이벤트를 호출하는 것이 훨씬 더 비싸다는 벤치 마크도 보았습니다.
— Greg D


2

Ian Griffiths 의이 기사는 그가 사용하지 말아야 할 깔끔한 트릭이라고 결론 내린 문제에 대한 두 가지 다른 해결책을 제공합니다.


3
그리고이 기사의 저자로서 C # 6이 null 조건 연산자 (?. 및. [])를 사용하여 즉시 해결하므로 지금은 사용하지 말아야한다고 덧붙였습니다.
— Ian Griffiths

2

제안 된 것과 같은 확장 확장 방법은 경합 조건 문제를 실제로 해결하지 않고 숨기는 방법입니다.

public static void SafeInvoke(this EventHandler handler, object sender)
{
    if (handler != null) handler(sender, EventArgs.Empty);
}

언급했듯이이 코드는 임시 변수가있는 솔루션과 동일하지만 ...

두 가지 문제 는 이벤트 구독자가 이벤트 구독을 취소 한 후 호출 될 수 있다는 것 입니다. 델리게이트 인스턴스가 임시 변수에 복사 된 후 (또는 위 메서드에서 매개 변수로 전달 된 후) 델리게이트가 호출되기 전에 구독 취소가 발생할 수 있기 때문에 가능합니다.

일반적으로 클라이언트 코드의 동작은 이러한 경우 예측할 수 없습니다. 구성 요소 상태에서 이미 이벤트 알림을 처리 할 수 ​​없습니다. 이를 처리하는 방식으로 클라이언트 코드를 작성하는 것은 가능하지만 클라이언트에게 불필요한 책임을 부과합니다.

스레드 안전성을 보장하는 유일한 방법은 이벤트 발신자에 대해 잠금 문을 사용하는 것입니다. 이렇게하면 모든 subscription \ unsubscriptions \ invocation이 직렬화됩니다.

보다 정확한 잠금을 위해서는 add \ remove 이벤트 접근 자 메서드에 사용 된 것과 동일한 동기화 개체에 적용되어야합니다. 기본값은 'this'입니다.


이것은 참조되는 경쟁 조건이 아닙니다. 경쟁 조건은 if (MyEvent! = null) // MyEvent가 이제 null이 아닙니다. MyEvent.Invoke (); // MyEvent는 이제 null입니다. 나쁜 일이 발생합니다. 따라서이 경쟁 조건에서는 작동이 보장되는 비동기 이벤트 핸들러를 작성할 수 없습니다. 그러나 '경쟁 조건'을 사용하면 작동이 보장되는 비동기 이벤트 처리기를 작성할 수 있습니다. 물론 구독자와 구독 취소 자에 대한 호출은 동기식이어야하며, 그렇지 않으면 잠금 코드가 필요합니다.
— jyoung

과연. 이 문제에 대한 나의 분석은 여기에 게시됩니다 : blogs.msdn.com/ericlippert/archive/2009/04/29/…
— Eric Lippert


1

아마도 더 좋지는 않지만 내 생각에 더 읽기 쉬운 것은 확장 메서드를 만드는 것입니다.

public static bool IsNull(this object obj) {
 return obj == null;
}

1
return obj == null의미 는 무엇입니까 . 그것은 반환 무엇
— 하마드 칸

1
이 경우 있음을 의미 obj입니다 null메소드가 리턴 true, 나는 생각한다.
— Joel

1
이것이 질문에 어떻게 대답합니까?
— Kugel 2014 년

답장이 늦었지만 이것이 효과가 있다고 확신합니까? 객체에 대한 확장 메서드를 호출 할 수 있습니까 null? 나는 이것이 유형의 자체 메서드에서 작동하지 않는다고 확신하므로 확장 메서드에서도 작동하지 않을 것입니다. 나는 물체 가 있는지 확인하는 가장 좋은 방법 null이 obj is null. 객체가 불행하게도, 확인 되지 아니 null불행한 괄호 안에 포장이 필요합니다.
— natiiix 19-06-24

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