(this == null) в С#!

Из-за ошибки, зафиксированной в С# 4, следующая программа печатает true. (Попробуйте в LINQPad)

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()) { }
}

В VS2008 в режиме Release он генерирует исключение InvalidProgramException. (В режиме отладки он работает нормально)

В VS2010 Beta 2 он не компилируется (я не пробовал Beta 1); Я узнал, что трудный путь

Есть ли другой способ сделать this == null в чистом С#?

Ответ 1

Это наблюдение было опубликовано на StackOverflow в еще одном вопросе ранее.

Marc отличный ответ на этот вопрос указывает, что согласно спецификации (раздел 7.5.7), вы не должны иметь доступ к this в этом контексте, и возможность сделать это в компиляторе С# 3.0 является ошибкой. Компилятор С# 4.0 ведет себя корректно в соответствии со спецификацией (даже в Beta 1, это ошибка времени компиляции):

§ 7.5.7 Этот доступ

Этот доступ состоит из зарезервированного слова this.

доступа:

this

Этот доступ разрешен только в блоке конструктора экземпляра, метода экземпляра или экземпляра экземпляра.

Ответ 2

Необработанная декомпиляция (рефлектор без оптимизаций) двоичного режима отладки:

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 не имеет смысла; если вы посмотрите на IL (ниже), он вызывает метод в пустой строке (!).

   .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 

В режиме Release локальная переменная оптимизирована, поэтому она пытается вставить несуществующую переменную в стек.

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

(Отражатель падает при повороте в С#)


EDIT: Кто-нибудь (Эрик Липперт?) знает, почему компилятор испускает ldloc?

Ответ 3

У меня это было! (и получил доказательство тоже)

alt text

Ответ 4

Это не "ошибка". Это вы злоупотребляете системой типов. Вы никогда не должны передавать ссылку на текущий экземпляр (this) любому, кто находится внутри конструктора.

Я мог бы создать подобную "ошибку", вызвав виртуальный метод в конструкторе базового класса.

Просто потому, что вы можете сделать что-то плохое, не означает его ошибка, когда вы получите бит.

Ответ 5

Я мог ошибаться, но я уверен, что если ваш объект null, никогда не будет сценария, в котором применяется this.

Например, как бы вы назвали CheckNull?

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

Ответ 6

Не уверен, что это то, что вы ищете

    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);
    }

example: UserID = CheckForNull (Request.QueryString [ "UserID" ], 147);