(this == null) в C #!

129

Из-за ошибки, исправленной в C # 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 в режиме выпуска возникает исключение InvalidProgramException. (В режиме отладки работает нормально)

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

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

SLaks
источник
3
Скорее всего, это ошибка компилятора C # 3.0. Это работает так, как должно быть в C # 4.0.
Мехрдад Афшари,
82
@SLaks: Проблема с ошибками в том, что вы можете ожидать, что они будут исправлены в какой-то момент, поэтому находить их «полезными», вероятно, неразумно.
AnthonyWJones
6
Спасибо! не знал о LINQPad. это круто!
thorn̈
8
В чем именно это полезно?
Аллен Райс,
6
чем была полезна эта ошибка?
BlackTigerX

Ответы:

73

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

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

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

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

это-доступ:

this

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

оборота Мехрдад Афшари
источник
2
Я не понимаю, почему в коде, представленном в этом вопросе, использование ключевого слова this недопустимо. Метод CheckNull - это обычный метод экземпляра, нестатический . Использование this в таком методе на 100% допустимо, и даже сравнение this с null допустимо. Ошибка в базовой строке инициализации: это попытка передать ограниченный экземпляром делегат в качестве параметра базовому ctor. Это ошибка (дыра в сематических проверках) в компиляторе: это НЕ должно быть возможным. Вам не разрешено писать, : base(CheckNull())если CheckNull не является статическим, и, аналогично, вы не должны иметь возможность встроить привязанную к экземпляру лямбду.
quetzalcoatl
4
@quetzalcoatl: thisв CheckNullметоде является законным. Что не является законным является неявным это доступом в () => CheckNull(), по существу () => this.CheckNull(), который работает вне блока из конструктора экземпляра. Я согласен с тем, что часть спецификации, которую я цитирую, в основном сосредоточена на синтаксической законности thisключевого слова, и, вероятно, другая часть решает эту проблему более точно, но ее также легко концептуально экстраполировать из этой части спецификации.
Mehrdad Afshari
2
Извините, я не согласен. Хотя я знаю это (и написал это в комментарии выше), и вы также это знаете - вы не упомянули фактическую причину проблемы в своем (принятом) ответе. Ответ принят - вроде бы и автор уловил. Но я сомневаюсь, что все читатели будут так же умны и свободно владеть лямбда-выражениями, чтобы с первого взгляда распознать instancebound-lambda по сравнению со static-lambda и сопоставить это с этим и проблемами с испускаемым IL :) Вот почему я добавил свои три цента. Кроме того, я согласен со всем остальным, что было обнаружено, проанализировано и описано вами и другими :)
quetzalcoatl
24

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

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 

(Рефлектор вылетает при превращении в C #)


РЕДАКТИРОВАТЬ : Кто-нибудь (Эрик Липперт?) Знает, почему компилятор выдает ldloc?

SLaks
источник
11

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

альтернативный текст

leppie
источник
2
Было поздно, это был знак, что я должен прекратить кодировать :) Взламывал наш с помощью DLR материал IIRC.
leppie
сделать визуализатор отладчика (DebuggerDisplay) для того, что есть 'this', и заставить вас обмануть это значение null? : D just sayin '
10

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

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

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


источник
14
Это ошибка компилятора. Он генерирует недопустимый IL. (Прочтите мой ответ)
SLaks
Контекст является статическим, поэтому на этом этапе вам не должно быть разрешено использовать ссылку на метод экземпляра.
leppie
10
@Will: это ошибка компилятора. Компилятор должен генерировать допустимый , проверяемый код для этого фрагмента кода или выдавать сообщение об ошибке. Когда компилятор ведет себя не в соответствии со спецификацией, он глючит .
Мехрдад Афшари,
2
@ Will # 4: Когда я писал код, я не думал о последствиях. Я понял, что это не имеет смысла, только когда он перестал компилироваться в VS2010. -
SLaks
3
Кстати, вызов виртуального метода в конструкторе - вполне допустимая операция. Это просто не рекомендуется. Это может привести к логической катастрофе, но никогда InvalidProgramException.
Мехрдад Афшари,
3

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

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

Derived derived = null;
Console.WriteLine(derived.CheckNull()); // this should throw a NullReferenceException
Дэн Тао
источник
3
В лямбде в аргументе конструктора. Прочтите весь фрагмент кода. (И попробуйте, если вы мне не верите)
SLaks
Я согласен, хотя я слабо помню что-то о том, как в C ++ объект не имел ссылки в своем конструкторе, и мне интересно, используется ли сценарий (this == null) в этих случаях, чтобы проверить, был ли вызов метода сделано из конструктора объекта перед выставлением указателя на «this». Хотя, насколько мне известно в C #, не должно быть случаев, когда this когда-либо будет иметь значение NULL, даже в методах Dispose или finalization.
jpierson
Я полагаю, что моя точка зрения состоит в том, что сама идея thisвзаимоисключающей возможности быть нулевым - своего рода "Cogito, ergo sum" компьютерного программирования. Поэтому ваше желание использовать это выражение this == nullи когда-либо вернуть его истинность кажется мне ошибочным.
Дэн Тао
Другими словами: я прочитал ваш код; Я хочу сказать, что я сомневаюсь в том, чего вы пытались достичь в первую очередь.
Дэн Тао,
Этот код просто демонстрирует ошибку и, как вы отметили, совершенно бесполезен. Чтобы увидеть настоящий полезный код, прочтите мой второй ответ.
SLaks
-1

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

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

пример: UserID = CheckForNull (Request.QueryString ["UserID"], 147);

Скотт и команда разработчиков
источник
13
Вы совершенно неправильно поняли вопрос.
SLaks
1
Я понял, как много. Думал, что все равно попробую.
Скотт и команда разработчиков,