(це == 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 в іншому питанні раніше.

Марк «s великий відповідь на це питання вказує на те, що в відповідності зі специфікацією (розділ 7.5.7), ви не повинні бути в змозі отримати доступ thisв цьому контексті і здатності зробити це в C # 3.0 компілятор це помилка. Компілятор C # 4.0 поводиться правильно відповідно до специфікації (навіть у бета-версії 1 це помилка часу компіляції):

§ 7.5.7 Цей доступ

Цей доступ складається із зарезервованого слова this.

цей доступ:

this

Цей доступ дозволений лише у блоці конструктора екземпляра, методу екземпляра або аксесуара екземпляра.


2
Я не бачу, чому в коді, представленому в цьому запитанні, використання ключового слова "це" недійсне. Метод CheckNull - це звичайний метод екземпляра, нестатичний . Використання "цього" в такому методі на 100% справедливе, і навіть порівняння цього з нулем є коректним. Помилка в базовому рядку init: це спроба передати обмежений екземпляром делегат як параметр базовому ctor. Це помилка (дірка в сематичних перевірках) у компіляторі: вона НЕ повинна бути можливою. Вам заборонено писати, : base(CheckNull())якщо CheckNull не є статичним, і ви не зможете вставити лямбда, прив'язану до примірника.
quetzalcoatl

4
@quetzalcoatl: thisв CheckNullметоді є законним. Що не є законним є неявним це доступом в () => CheckNull(), по суті () => this.CheckNull(), який працює поза блоку з конструктора примірника. Я погоджуюся, що частина специфіки, яку я цитую, здебільшого зосереджена на синтаксичній законності thisключового слова, і, ймовірно, інша частина вирішує це питання точніше, але концептуально екстраполювати також і цю частину специфікації.
Мехрдад Афшарі

2
Вибачте, я не згоден. Хоча я це знаю (і написав це у коментарі вище), і ви також це знаєте - ви не вказали фактичної причини проблеми у своїй (прийнятій) відповіді. Відповідь прийнята - так, здавалося б, автор також її зрозумів. Але я сумніваюся, що всі читачі будуть настільки ж яскравими та вільними в лямбдах, щоб розпізнати екземпляр-лямбда проти статичного-лямбда з першого погляду і відобразити це на «це» та проблеми з випромінюваною ІЛ :) Тому я додав свої три центи. Окрім цього, я погоджуюся з усім іншим, що було знайдено, проаналізовано та описано вами та іншими :)
quetzalcoatl

24

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

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 

У режимі випуску локальна змінна оптимізована, тому вона намагається натиснути на стек неіснуючу змінну.

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

(Рефлектор виходить з ладу при перетворенні його на C #)


EDIT : Хто-небудь (Ерік Ліпперт?) Знає, чому компілятор випускаєldloc ?


11

У мене це було! (і отримав доказ теж)

alt текст


2
Запізнився, був знаком, що я повинен припинити кодування :) Зламував наші речі з DLR IIRC.
леппі

зробіть візуалізатор налагодження (DebuggerDisplay) для будь-якого "цього", і зробіть з цього дурня, що це нуль? : D just sayin '

10

Це не "помилка". Це ви зловживаєте системою типів. Ви ніколи не повинні передавати посилання на поточний екземпляр ( this) комусь із конструкторів.

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

Тільки тому, що ви можете зробити щось погане, це не означає його помилку, коли вас покусає.


14
Це помилка компілятора. Це генерує недійсний ІЛ. (Прочитайте мою відповідь)
SLaks

Контекст є статичним, тому на цьому етапі вам не слід допускати посилання методу екземпляра.
леппі

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 ++ об’єкт не мав посилання в його конструкторі, і мені цікаво, чи використовується сценарій (цей == null) у тих випадках, щоб перевірити, чи був виклик методу зроблені з конструктора об'єкта перед тим, як виставити покажчик на "це". Хоча, наскільки я знаю в C #, не повинно бути жодних випадків, коли "це" коли-небудь буде нульовим, навіть у методах розпорядження чи доопрацювання.
jpierson

Думаю, моя думка полягає в тому, що сама ідея thisвзаємно не виключає можливості бути недійсним - свого роду "Cogito, ergo sum" комп'ютерного програмування. Тому ваше бажання використовувати вираз this == nullі коли-небудь повернути його справжнє вражає мене як неправильне.
Дан Дао

Іншими словами: я читав ваш код; що я говорю, це те, що я питаю, що ви намагалися досягти в першу чергу.
Дан Дао

Цей код просто демонструє помилку, і, як ви вказали, є абсолютно марним. Щоб побачити справжній корисний код, прочитайте мою другу відповідь.
СЛАкс

-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
Ви повністю зрозуміли питання.
СЛАкс

1
Я так багато рахував. Думав, я все одно спробую.
Скотт і Команда Dev
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.