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? 나는 이것이 유형의 자체 메서드에서 작동하지 않는다고 확신하므로 확장 메서드에서도 작동하지 않을 것입니다. 나는 물체 있는지 확인하는 가장 좋은 방법 nullobj is null. 객체가 불행하게도, 확인 되지 아니 null불행한 괄호 안에 포장이 필요합니다.
natiiix 19-06-24

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