2009-10-21 4 views
123

C# 4에서 수정 된 버그로 인해 다음 프로그램은 true을 인쇄합니다. 그것이 InvalidProgramException를 throw, 릴리스 모드에서 VS2008에서(this == null) C#!

void Main() { new Derived(); } 

class Base { 
    public Base(Func<string> valueMaker) { Console.WriteLine(valueMaker()); } 
} 
class Derived : Base { 
    string CheckNull() { return "Am I null? " + (this == null); } 
    public Derived() : base(() => CheckNull()) { } 
} 

을 (LINQPad에보십시오). (디버그 모드에서 제대로 작동합니다.)

VS2010 Beta 2에서는 컴파일되지 않습니다 (베타 1을 시도하지 않았습니다). 어려운 방법을 배웠습니다

this == null을 순수한 C#으로 만들 수있는 다른 방법이 있습니까?

+3

C# 3.0 컴파일러의 버그 일 가능성이 큽니다. C# 4.0에서 작동해야하는 방식으로 작동합니다. –

+0

예, 모든 IMO에서 컴파일하지 않아야합니다. – leppie

+0

무엇 ...?! 누가 그런 아이디어를 내놓습니까? 그럼에도 불구하고 재미있는 버그, 공유 덕분에 –

답변

70

이 관찰은 오늘 StackOverflow에 another question에 오늘 게시되었습니다.

Marcgreat answer to that question은 사양 (섹션 7.5.7)에 따라, 당신이 그 상황과 3.0 컴파일러 버그가 C#으로 그렇게 할 수있는 능력 this에 액세스 할 수 없습니다 것을 나타냅니다. C# 4.0 컴파일러는 사양에 따라 올바르게 작동하고 (심지어 베타 1이 컴파일 시간 에러이다)이 액세스

§ 7.5.7은이 액세스이 구성 예약어 this.

이 접속 :

this 

이 액세스은 인스턴스 생성자 인스턴스 메소드 또는 인스턴스 접근의 블록 허용된다. 이것은 당신이

public static T CheckForNull<T>(object primary, T Default) 
    { 
     try 
     { 
      if (primary != null && !(primary is DBNull)) 
       return (T)Convert.ChangeType(primary, typeof(T)); 
      else if (Default.GetType() == typeof(T)) 
       return Default; 
     } 
     catch (Exception e) 
     { 
      throw new Exception("C:CFN.1 - " + e.Message + "Unexpected object type of " + primary.GetType().ToString() + " instead of " + typeof(T).ToString()); 
     } 
     return default(T); 
    } 

예를 찾고있는 경우

+2

이 질문에 제시된 코드에서 키워드 "this"의 사용이 유효하지 않은 이유는 무엇입니까? CheckNull 메소드는 일반적인 인스턴스 메소드 인 ** nonstatic **입니다. "this"를 사용하면 100 % 유효하며 null과 비교하는 경우에도 유효합니다. 기본 초기화 줄에 오류가 있습니다. 인스턴스 기반 대리자를 매개 변수로 기본 ctor에 전달하려고합니다. 이것은 컴파일러의 버그 (sematic check에서의 구멍)입니다 : 불가능합니다. CheckNull이 정적이 아닌 경우': base (CheckNull()) '을 쓸 수 없으며, 인스턴스 바인딩 된 람다를 인라인 할 수 없어야합니다. – quetzalcoatl

+4

@quetzalcoatl :'CheckNull' 메쏘드의'this'는 합법적입니다. 불법적 인 것은'() => CheckNull()'에있는 ** 암시적인 ** * this-access *입니다. ** 블록 밖에서 실행되고있는 본질적으로'() => this.CheckNull()'입니다. 인스턴스 생성자의 필자가 지적한 스펙 부분은 대부분이 키워드의 구문 적 합법성에 초점을 맞추고 있으며, 아마도이 부분을 좀 더 정확하게 다룰 부분 일지는 모르겠다. 그러나 스펙의이 부분을 개념적으로 추론하기는 쉽다. –

+1

죄송합니다, 제가 disgree. 그 사실을 알고 있지만 (위의 설명에 그 내용을 적어 두었습니다.) 또한 여러분도 알고 있습니다 - 여러분은 (받아 들인) 대답에서 문제의 실제 원인을 언급하지 않았습니다. 대답은 받아 들여진다. 그래서 겉으로보기에는 저자도 그것을 잡았다. 그러나 나는 모든 독자가 람다처럼 밝고 유창해서 첫눈에 instance-lambda 대 static-lambda를 인식하고 'this'와 일리노이 문제에 매핑한다는 것을 의심하지 않습니다. 이것이 내가 세 개의 센트를 추가 한 이유입니다. 그 외에 나는 당신과 다른 사람들에 의해 발견되고, 분석되고 묘사 된 모든 것에 동의합니다. – quetzalcoatl

4

내가 틀릴 수도 있지만, 나는 당신의 객체가 null 결코 this가 적용되는 시나리오가있을 않을거야 경우 확신합니다.

예를 들어 CheckNull을 어떻게 부르겠습니까?

Derived derived = null; 
Console.WriteLine(derived.CheckNull()); // this should throw a NullReferenceException 
+3

constructor 인수의 lambda에 있습니다. 전체 코드 스 니펫을 읽으십시오. (그리고 당신이 나를 믿지 않는다면 시도해라.) – SLaks

+0

나는 C++에서 객체가 생성자 내에서 참조를 가지지 않았 음에 대해 희미하게 기억하고 있지만 (this == null) 시나리오가 있는지 궁금하다. 이러한 경우에 포인터가 "this"에 노출되기 전에 메서드에 대한 호출이 객체의 생성자에서 수행되었는지 여부를 확인하는 데 사용됩니다. 비록 C#에서 알 수 있듯이 "This"가 null이 될 수있는 경우는 없으며 Dispose 또는 Finalization 메서드에서도 마찬가지입니다. – jpierson

+0

null 값은 올바른 순간에 캡처됩니다. –

10

나는 그것을 가지고있다! 디버그 모드 바이너리의

alt text

+0

어떻게 했습니까? – SLaks

+2

늦었습니다. 코딩을 중지해야한다는 표시였습니다. DLR stuff IIRC를 해킹했습니다. – leppie

+0

'this'가 무엇이든지 디버거 비주얼 라이저 (DebuggerDisplay)를 만들고 그 바보가 null인지 확인하십시오. : D just sayin ' –

23

(아무 최적화와 리플렉터) 원시 디 컴파일 (너무 증거를 가지고)입니다 :

private class Derived : Program.Base 
{ 
    // Methods 
    public Derived() 
    { 
     base..ctor(new Func<string>(Program.Derived.<.ctor>b__0)); 
     return; 
    } 

    [CompilerGenerated] 
    private static string <.ctor>b__0() 
    { 
     string CS$1$0000; 
     CS$1$0000 = CS$1$0000.CheckNull(); 
    Label_0009: 
     return CS$1$0000; 
    } 

    private string CheckNull() 
    { 
     string CS$1$0000; 
     CS$1$0000 = "Am I null? " + ((bool) (this == null)); 
    Label_0017: 
     return CS$1$0000; 
    } 
} 

CompilerGenerated 방법은 이해가되지 않습니다; 일리노이 (아래)를 보면 NULL 문자열을 (!)으로 호출하고 있습니다.

.locals init (
     [0] string CS$1$0000) 
    L_0000: ldloc.0 
    L_0001: call instance string CompilerBug.Program/Derived::CheckNull() 
    L_0006: stloc.0 
    L_0007: br.s L_0009 
    L_0009: ldloc.0 
    L_000a: ret 

릴리스 모드에서는 로컬 변수가 최적화되어 있으므로 존재하지 않는 변수를 스택에 푸시하려고 시도합니다.

L_0000: ldloc.0 
    L_0001: call instance string CompilerBug.Program/Derived::CheckNull() 
    L_0006: ret 

(반사경 충돌의 C#으로 돌려)


편집 :합니까 사람 (? 에릭 Lippert의) 컴파일러가 ldloc 왜 방출 알아?

10

이것은 "버그"가 아닙니다. 너 타입 시스템을 악용하는거야. 현재 인스턴스 (this)에 대한 참조를 생성자 내의 모든 사용자에게 전달하지 않아야합니다.

기본 클래스 생성자 내에서 가상 메서드를 호출하여 비슷한 "버그"를 만들 수 있습니다.

당신이 당신이 그것에 의해 비트를 얻을 때 그에게 버그을 의미하지 않는다 나쁜 일을 할 수해서.

+14

이것은 컴파일러 버그입니다. 유효하지 않은 IL을 생성합니다. (내 대답 읽기) – SLaks

+0

컨텍스트는 정적이므로 해당 단계에서 인스턴스 메서드 참조가 허용되면 안됩니다. – leppie

+0

절대로하면 안되는 일을하고 휴식을 취하십시오. 그리고 그것은 컴파일러 버그입니까? 다시 말하지만, 컴파일러가 당신의 연기를 심하게 다루지 못하기 때문에 그것이 버그라는 것을 의미하지는 않습니다. – Will

-1

확실하지 : 사용자 ID = CheckForNull (Request.QueryString을 [ "사용자 ID", 147);

+13

당신은 _ 그 질문을 완전히 오해했습니다. – SLaks

+1

나는 많이 생각했다. 어쨌든 내가 시도 할 줄 알았다. –