2010-01-07 3 views
32

가능한 복제를 null로 :
C# okay with comparing value types to null비교 구조체는

나는 다중 스레드 환경에서 윈도우 응용 프로그램에서 일하고 때로는 예외 "호출 또는 BeginInvoke는 컨트롤을 호출 할 수 없습니다를 얻을 것 창 핸들을 만들 때까지. "

if(this.Handle != null) 
{ 
    //BeginInvokeCode 
} 

하지만 그래도 문제가 해결되지 않은 : 그래서 난 그냥이 코드 줄을 추가 거라고 생각. 그래서 조금 더 파고, IntPtr (Form.Handle이있는 타입)은 nullable이 될 수없는 구조체라는 사실을 깨달았습니다. 이것은 수정 된 문제였습니다.

if(this.Handle != IntPtr.Zero) 
{ 
    //BeginInvokeCode 
} 

그렇다면 왜 내가 null을 검사 할 때 컴파일 했습니까? 그래서 내가 직접 시도하기로 결정 후

public struct Foo { } 

과 :

static void Main(string[] args) 
    { 
     Foo f = new Foo(); 
     if (f == null) { } 
    } 

하고 "오류 1 연산자 '==는'타입의 피연산자에 적용 할 수 없다는 컴파일하지 않았다 충분히 확인 'ConsoleApplication1.Foo'및 '' ". 좋아, 그럼 IntPtr에 대한 메타 데이터를보고 IntPtr 구조체 (ISerializable, ComVisible)에 있었지만 아무 것도 도움이되지 않은 Foo 구조체에 모든 것을 추가하기 시작했습니다. ! 내가 ==의 연산자 오버로딩을 추가 할 때 마지막으로하고, =, 그것은 일 :

[Serializable] 
[ComVisible(true)] 
public struct Foo : ISerializable 
{ 
    #region ISerializable Members 

    public void GetObjectData(SerializationInfo info, StreamingContext context) 
    { 
     throw new NotImplementedException(); 
    } 

    #endregion 

    public override bool Equals(object obj) 
    { 
     return base.Equals(obj); 
    } 

    public override int GetHashCode() 
    { 
     return base.GetHashCode(); 
    } 

    public static bool operator ==(Foo f1, Foo f2) { return false; } 
    public static bool operator !=(Foo f1, Foo f2) { return false; } 
} 

이 마지막으로 컴파일 :

static void Main(string[] args) 
    { 
     Foo f = new Foo(); 
     if (f == null) { } 
    } 

내 질문은 왜? 왜 == 및! =를 대체하면 null과 비교할 수 있습니까? == 및! =에 대한 매개 변수는 여전히 Foo 유형이며 nullable이 아니므로 이것이 왜 갑자기 허용 되었습니까?

+2

+1 좋은 질문 btw, 그리고 동료 브루클린에서 안녕하세요 :) –

답변

14

는 MS가 nullable 형식을 도입 할 때, 그들은 모든 구조체는 nullable 형식 (foo?)에 암시 적으로 convertable 수 있도록 그것을 만든, 그래서

if(f == null) 

에 해당 코드이다
if ((Nullable<foo>)f == (Nullable<foo>)null) 

MSDN은 "값 형식에 존재하는 사용자 정의 연산자도 null 가능 형식에서 사용될 수 있습니다"라고 말했기 때문에 operator==을 재정의 할 때 사용자 정의 된대로 암시 적 캐스트를 컴파일 할 수 있습니다 == - 무료로 널값 과부하를줍니다.옆

:

ldc.i4.0 
ldc.i4.0 
ceq 
stloc.1 //where there is an unused boolean local 

:

귀하의 예처럼 보인다,이 IL 일부 컴파일러 최적화 도 시험이 있었다 힌트를 컴파일러에 의해 방출되는 유일한 존재입니다 주를

Foo f = new Foo(); 
object b = null; 
if (f == b) { Console.WriteLine("?"); } 

으로 변경하면 더 이상 컴파일되지 않습니다. 당신이 구조체 상자한다면 :

Foo f = new Foo(); 
object b = null; 
if ((object)f == b) { Console.WriteLine("?"); } 

경우 컴파일, IL을 방출, 그리고 예상대로 실행 (구조체가 null 결코);

2

나는 특정 연산자와 필요한 모든 논리를 처리한다는 개념에 명시 적으로 가입하고 있다고 생각합니다. 따라서 연산자 충돌이 발생하면 연산자 오버로드 메서드에서 null을 처리하는 것은 사용자의 책임입니다. 이 경우에는 null로 비교하면 오버로드 된 메서드가 결코 부딪치지 않는다는 것을 알았을 것입니다.

정말 흥미로운 점은 Henks answer here 다음과 같은 점은 반사경에서 다음 코드를 확인한 것입니다.

Foo f1 = new Foo(); 
if(f1 == null) 
{ 
    Console.WriteLine("impossible"); 
} 

Console.ReadKey(); 

반사경이 보여준 것입니다.

Foo f1 = new Foo(); 
Console.ReadKey(); 

컴파일러가이를 정리하면 오버로드 된 연산자 메서드가 호출되지 않습니다.

+0

... 물론 구조체 매개 변수가 실제로 null이 될 수 있다는 것을 제외하고, 따라서 그의 질문. –

+0

맞아,하지만 컴파일러가 처리한다고 생각하지 않는다. 컴파일러가 오퍼레이터에 과부하가 걸렸다는 것을 의미한다. 또한 메소드는 결코 호출되지 않습니다. –

1

구조체는 오버로드 "=="를 정의하지 않거나 "! =" 그래서 원래의 오류가 발생했습니다. 오버로드가 구조체에 추가되면 비교는 합법적입니다 (컴파일러가 예상 함). 연산자 과부하의 생성자는이 논리를 처리하는 책임이 있습니다 (분명히 Microsoft는이 경우이를 놓쳤습니다).

구조체의 구현 (및 그 의미)에 따라 널 (null)과의 비교가 완벽하게 유효 할 수 있으므로 이것이 가능합니다. 이 수를 선택할 수

public static bool operator ==(object o1, object o2) 

public static bool operator ==(Foo f1, Foo f2) 

과 모두 그 :

+0

아니, 우리는 이것을 놓치지 않았다. (경고가보고되지 않는다는 사실은 버그 인 것 같습니다.)이 동작은 사양에 따라 정확합니다. –

+0

@Eric, 알아두면 좋네. 고마워요 –

3

내가 생각할 수있는 모든 == 연산자의 당신의 오버로드가 컴파일러에게 사이에서 선택을 제공한다는 것입니다 왼쪽을 물체에 던지고 전을 사용하십시오. 물론 코드를 기반으로 무언가를 실행하려고하면 연산자 오버로드가 발생하지 않습니다. 연산자 사이에 선택의 여지없이, 컴파일러는 분명히 몇 가지 추가 점검을 수행하고 있습니다.

+0

과부하가 없으면 첫 번째 양식이 계속 적용될 수 있지만 컴파일러는이를 금지합니다. 나는 네가 확실히 바른 길을 가고 있다고 생각하지만 여기에는 더 깊은 마법이있다. –

+2

실제로 (obj, obj), (Foo, Foo)와 (Foo?, Foo?) 사이의 선택은 (Foo, Foo)에 대한 항등 연산자를 정의하면 해제 된 버전으로 자동 정의됩니다. –

+0

@Eric - 완전한 설명은 총체적인 의미를가집니다. 감사. –

5

이것은 직렬화 또는 COM과 아무 관련이 없으므로 방정식에서 제거 할 가치가 있습니다. 예를 들어, 여기에 문제를 보여줍니다 짧지 만 완전한 프로그램입니다 :

Foo f = new Foo(); 
Foo? g = null; 
Console.WriteLine(f == g); 
:

using System; 

public struct Foo 
{ 
    // These change the calling code's correctness 
    public static bool operator ==(Foo f1, Foo f2) { return false; } 
    public static bool operator !=(Foo f1, Foo f2) { return false; } 

    // These aren't relevant, but the compiler will issue an 
    // unrelated warning if they're missing 
    public override bool Equals(object x) { return false; } 
    public override int GetHashCode() { return 0; } 
} 

public class Test 
{ 
    static void Main() 
    { 
     Foo f = new Foo(); 
     Console.WriteLine(f == null); 
    } 
} 

을 내가 거기 Nullable<Foo> 리터럴 널에서 암시 적 변환 그리고 당신이 법적으로이 작업을 수행 할 수 있기 때문에이 컴파일 생각

==이 오버로드 된 경우에만 이것이 발생한다는 것은 흥미 롭습니다. Marc Gravell은 전에 이것을 발견했습니다. 나는 그것이 실제로 인지, 아니면 컴파일러 버그인지 또는 변환, 과부하 등이 해결되는 방식으로 매우 미묘한 것인지 알지 못합니다.

에서 일부가지 경우 (예를 들어 int, decimal) 컴파일러는 암시 적 변환에 대해 경고합니다 - 그러나 다른 사람 (예를 들어 Guid)에서하지 않습니다.

그것은 문제처럼 보인다
+0

@Jon,이 동작을 시뮬레이트하기 위해 Equals 또는 GetHashCode를 재정의 할 필요가 없었습니다. –

+1

@Stan : 아니오, 방금 경고없이 컴파일 할 수있게하려고했습니다. 정확히 그 대답을 편집하려고했다 :) –