(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 #?


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?


11

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

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


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
Я понял, как много. Думал, что все равно попробую.
— Скотт и команда разработчиков,
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.