(це == 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.