이 두 줄이 같은가요? … : 'vs'?? '?


80

이 두 줄에 차이가 있습니까?

MyName = (s.MyName == null) ? string.Empty : s.MyName

또는

MyName = s.MyName ?? string.Empty

8
그들은 동일한 코드를 생성합니다하거나 다르게 처리되고 있는지 방법 당신은 더 좋은 생각이있을 것이라는 점을, 그들의 IL 확인
manman

12
@manman 누군가가 높은 수준의 코드를 이해하지 못한다면 IL 코드가 읽기 쉽다 는 인상을주는 것은 무엇 입니까?
Servy

11
@Servy 어떤 사람들은 차이점을 배우는 것에 대해 호기심이 많고 다른 사람들은 높은 수준의 토론을 다루고있을 때 차이점을 이해하는 다른 방법을 지적하는 것이 좋습니다. 그리고 그가 매우 호기심이 많다면 가서 결과를 비교할 수 있습니다. IL 전체를 읽어야합니다
manman 2014 년

6
유일한 차이점은 s.MyName한 번 또는 두 번 평가하는지 여부 입니다.
Tim S.

2
당신이 IL 코드를 이해 할 필요가 없기 때문에 @Servy, 당신은 단지에있는 diff그것
wchargin

답변:


166

업데이트 : 나는이 주제를 더 깊이 논의하는 블로그 포스트를 썼습니다. http://www.codeducky.org/properties-fields-and-methods-oh-my/


일반적으로 동일한 결과를 반환합니다. 그러나 첫 번째 예제 MyName에서는 MyNamegetter가 두 번 실행되고 두 번째 예제에서는 한 번만 실행 되기 때문에 속성 일 때 눈에 띄는 차이가 발생하는 몇 가지 경우 가 있습니다.

예를 들어, MyName두 번 실행하면 성능 차이가 발생할 수 있습니다 .

string MyName
{
    get 
    {
        Thread.Sleep(10000);
        return "HELLO";
    }
}

또는 상태 저장 인 MyName경우 두 번 실행하면 다른 결과를 얻을 수 있습니다 MyName.

private bool _MyNameHasBeenRead = false;

string MyName
{
    get 
    {
        if(_MyNameHasBeenRead)
                throw new Exception("Can't read MyName twice");
        _MyNameHasBeenRead = true;
        Thread.Sleep(10000);
        return "HELLO";
    }
}

또는 다른 스레드에서 변경 MyNameMyName수있는 경우 두 번 실행하면 다른 결과를 얻을 수 있습니다.

void ChangeMyNameAsync()
{
    //MyName set to null in another thread which makes it 
    //possible for the first example to return null
    Task.Run(() => this.MyName = null);
}

string MyName { get; set; }  

실제 코드가 컴파일되는 방법은 다음과 같습니다. 먼저 삼항식이있는 조각 :

IL_0007:  ldloc.0     // s
IL_0008:  callvirt    s.get_MyName       <-- first call
IL_000D:  brfalse.s   IL_0017
IL_000F:  ldloc.0     // s
IL_0010:  callvirt    s.get_MyName       <-- second call
IL_0015:  br.s        IL_001C
IL_0017:  ldsfld      System.String.Empty
IL_001C:  call        set_MyName

다음은 null-coalescing 연산자가있는 부분입니다.

IL_0007:  ldloc.0     // s
IL_0008:  callvirt    s.get_MyName       <-- only call
IL_000D:  dup         
IL_000E:  brtrue.s    IL_0016
IL_0010:  pop         
IL_0011:  ldsfld      System.String.Empty
IL_0016:  call        s.set_MyName

보시다시피 삼항 연산자에 대한 컴파일 된 코드는 속성 값을 얻기 위해 두 번의 호출을 수행하는 반면 null 통합 연산자는 1 만 수행합니다.


3
MyName = s.MyName ?? string.EmptyMyName속성 이면 getter를 두 번 실행하지 않습니다 . 따라서 getter를 두 번 실행하지 않으려면 두 번째 줄을 사용해야합니다.
Steven Wexler 2014 년

2
두 코드의 IL 예제를 자유롭게 추가했습니다. 전체 LINQPad 예제는 dropbox.com/s/x6zqdsjlkosxchf/SO21052437.linq
Lasse V. Karlsen을 참조하십시오.

1
IL 코드의 마지막 부분에서 "s"를 놓친 것 같습니다. 현재 읽어야 et_MyName합니다 set_MyName.
AJMansfield 2014 년

2
또한 위의 이유로 인해 긴 버전 ?:이 실제로 발생할 수 있습니다 null. "Value from other thread"위의 내용이 실제로 null문자열 이라면 더 잘 설명 할 수 있습니다 . 그러면 결과 null 일 있음을 보여 줍니다. 객체가 접근 자의 첫 번째 호출에서 문자열 인스턴스를 get반환하고 두 번째 호출에서만 null 을 반환하기로 결정한 경우 상태 저장 객체에서도 마찬가지 일 수 있습니다.
Jeppe Stig Nielsen

2
또한 C # 4.0 언어 사양은 MyValue가 실제로 두 번 평가된다는 것을 보장합니다 (§7.14). 따라서 지터가 이중 호출을 최적화하지 못하도록 방지합니다 (처음에는 생각
했듯이

26

속성이 단순한 getter 이상인 경우 첫 번째 항목에 대해 null이 아닌 경우 함수를 두 번 실행할 수 있습니다.

속성이 상태 저장 객체에있는 경우 속성에 대한 두 번째 호출은 다른 결과를 반환 할 수 있습니다.

class MyClass
{
    private IEnumerator<string> _next = Next();

    public MyClass()
    {
        this._next.MoveNext();
    }

    public string MyName
    {
        get
        {
            var n = this._next.Current;
            this._next.MoveNext();
            return n;
        }
    }


    public static IEnumerator<string> Next()
    {
        yield return "foo";
        yield return "bar";
    }
}

또한 문자열이 아닌 경우 클래스는 삼항 연산자와 다른 작업을 수행하기 위해 ==를 오버로드 할 수 있습니다. 삼항 연산자가 오버로드 될 수 있다고 생각하지 않습니다.


삼항 연산자는 확실히 오버로드 될 수 없습니다. 사실상 다른 모든 연산자를 오버로드 할 수있는 C ++조차도 오버로드 할 방법이 없습니다 ?:. 나는 어떤 언어도 보지 못했습니다.
Darrel Hoffman

) @DarrelHoffman 항상 당신이 자신의 롤 수 스칼라,있다
jdphenix

9

유일한 차이점은 s.MyName두 번 또는 한 번 평가하는지 여부 입니다. 첫 번째는 s.MyNamenull이 아닌 경우 두 번 수행하고 두 번째는 한 번만 평가합니다.

대부분의 경우이 차이는 중요하지 않으며, 더 명확하고 간결하기 때문에 두 번째를 사용하겠습니다.


5

예, 둘 다 동일하며 null 통합 연산자 입니다.

피연산자가 null이 아니면 왼쪽 피연산자를 반환합니다. 그렇지 않으면 오른손 피연산자를 반환합니다.

효율성에 대해 이야기하면

string MyName = (s.MyName == null) ? string.Empty : s.MyName;
string MyName2 = s.MyName ?? string.Empty;

dissembler를 사용 하면 첫 번째 명령문 은 컴파일러에서 19 개의 명령문 을 실행 해야하는 반면 두 번째 명령문은 12 개의 명령문 만 실행하면된다는 것을 알 수 있습니다.


이름을 알려 주셔서 감사합니다. Java에도이 기능이 있는지 확인하고 싶었습니까? 운영자. 그리고 그렇지 않습니다. 그래서 저는 두 가지 모두에 대해 삼항 연산자를 고수 할 것입니다.
developerwjk


0

그들은 동일한 작업을 수행합니다.

유일한 차이점은 동료 또는 코드를 읽는 사람이 구문을 이해하는지 여부에 대한 가독성입니다.

편집 : 또한 첫 번째 옵션은 속성을 MyName두 번 평가할 수 있습니다 .


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