Почему '{' генерирует исключение NullReferenceException в статическом методе?

Это своего рода эзотерика. Я столкнулся с NullReferenceException при попытке открыть форму (в конструкторе winforms) в проекте winforms в Visual Studio 2008. Трассировка стека указывает на четвертую строку следующего кода:

public static class Logger
{
    public static void LogMethodEnter()
    {
        var frame = new StackFrame(1);
        var method = frame.GetMethod();
        Trace.TraceInformation("{0}.{1}.{2}()", method.DeclaringType.Namespace, method.DeclaringType.Name, method.Name);
        Trace.Indent();
    }

    public static void LogMethodExit()
    {
        Trace.Unindent();
    }
}

...имеется в виду строка с открывающей фигурной скобкой. Я столкнулся с той же проблемой (но не с участием дизайнера winforms) в других проектах, и я думаю, что это проблема, связанная с потоками, но у меня нет кода для ее воспроизведения.

Почему это происходит и почему трассировка стека исключений указывает на строку с фигурной скобкой?

Пояснение. Исключение нулевой ссылки возникает только в конструкторе winforms. Когда приложение запускается, оно не выдает эту ошибку.


person Zachary Yates    schedule 26.08.2010    source источник
comment
На разборке смотреть не пробовали? Возможно, вы сможете увидеть, что он там делает (хотя у меня не было большого успеха в том, чтобы заставить символы работать в дизассемблированном представлении). Существует также небольшая вероятность того, что вы используете код, который не соответствует вашему исходному коду.   -  person Rup    schedule 26.08.2010
comment
Вы пробовали очистить решение и восстановить?   -  person FrustratedWithFormsDesigner    schedule 26.08.2010
comment
@Rup: Согласен с тем, что код, на который жалуется VS, не синхронизирован с файлом - может быть, он работает с чем-то, что кэшировано?   -  person FrustratedWithFormsDesigner    schedule 26.08.2010
comment
@Rup, @Frustrated: Да, пробовал очистить/перестроить. Также попытался закрыть VS и снова открыть проект. У меня была ситуация, которую вы описываете, случалась раньше, но закрытие/повторное открытие обычно работало. Любые идеи о том, как убедиться, что код сборки/объекта не кэшируется?   -  person Zachary Yates    schedule 26.08.2010
comment
Можете ли вы показать, что еще есть в вашем классе?   -  person Dirk Vollmar    schedule 26.08.2010
comment
Избегайте запуска подобного кода во время разработки. Используйте свойство Control.DesignMode.   -  person Hans Passant    schedule 26.08.2010
comment
Вам также необходимо подавить встраивание с помощью атрибута [MethodImpl].   -  person Hans Passant    schedule 26.08.2010
comment
Можно ли поделиться минимальным образцом для воспроизведения поведения? У меня с текущим образцом в дизайнере все работает нормально.   -  person Dirk Vollmar    schedule 26.08.2010


Ответы (5)


Я предполагаю, что номера строк отключены (фактическая причина этого не так важна), и исключение фактически выдается этим выражением:

method.DeclaringType.Namespace

И причина, по которой вы можете увидеть исключение NullReference, заключается в том, что выражение new StackFrame(1) на пару строк раньше может иногда возвращать пустой фрейм. Пустой фрейм означает, что вызов .GetMethod() вернет null, и все.

Причина, по которой вы иногда получаете пустой фрейм, заключается в том, что JIT-компилятор может выбрать встраивание коротких, многократно вызываемых методов, таких как тот, что находится в вашем коде. Это сбросит ваш стек вызовов, поэтому в лучшем случае вы получите метод более высокого уровня, чем предполагалось, или в худшем случае (в вашем методе Main) нет более высокого метода, и вы получите нуль.

person Joel Coehoorn    schedule 26.08.2010
comment
Интересный. Значит ли это, что приложение выдает это исключение во время выполнения? Я не видел такого поведения, полагаю, мне следует уточнить, что исключение возникает ТОЛЬКО, когда я пытаюсь открыть форму в конструкторе winforms. - person Zachary Yates; 26.08.2010
comment
@Zach Я думаю, что исключение возможно во время выполнения, но вы, вероятно, никогда не увидите его в winforms, потому что вы вряд ли будете использовать вызов этого из метода Main в верхней части стека. Вместо этого вы можете увидеть некоторые неправильные записи журнала в местах, где это встроено. Вы видите это в дизайнере, потому что Visual Studio показывает, как Visual Studio запускает код, чтобы выяснить поведение/внешний вид дизайнера. Внезапно встроенный вызов запускается прямо на вершине стека. - person Joel Coehoorn; 26.08.2010
comment
Спасибо за понимание, я не знал о встраивании - я попробую атрибут [MethodImpl] и некоторую проверку нулей. - person Zachary Yates; 26.08.2010
comment
У меня была такая же мысль, но как добавление статического конструктора может решить эту проблему? И я сомневаюсь, что дизайнер встраивает код... - person Dirk Vollmar; 26.08.2010
comment
Я решил проблему, объединив ответы от всех — я принял этот, потому что в нем больше всего информации. Я использовал предложение @Hans Control.DesignMode, предложение @0xA3 .pdb устарело, чтобы изменить ошибку при изменении кода, @Joel проверка нуля и предложение встраивания компилятора - person Zachary Yates; 27.08.2010

Я предполагаю, что у вас есть инициализация статического члена где-то в вашем классе, и этот инициализатор выдает NullReferenceException. Кроме того, я предполагаю, что у вас нет статического конструктора, поэтому ваш объект помечен как beforefieldinit, и поэтому NullReferenceException выбрасывается, а ваш метод, который его использует, JITed.

Что-то типа:

public static class Logger
{
    private static object x = InitObjectX();
    private static object InitObjectX() {
        x.GetHashCode(); // Will throw since x is null.
    }

    public static void LogMethodEnter() 
    { 
        var frame = new StackFrame(1); 
        var method = frame.GetMethod(); 
        Trace.TraceInformation("{0}.{1}.{2}()", method.DeclaringType.Namespace, method.DeclaringType.Name, method.Name); 
        Trace.Indent(); 
    } 

    public static void LogMethodExit() 
    { 
        Trace.Unindent(); 
    } 
} 
person erikkallen    schedule 26.08.2010
comment
Хм, есть пара статических полей, но инициализируются они так: static string Name = xyz; Это вызовет ту же проблему? - person Zachary Yates; 26.08.2010
comment
Я думал о том же, но исключение во время инициализации статического класса обычно приводит к TypeInitializationException (с NRE в качестве внутреннего исключения). - person Dirk Vollmar; 26.08.2010

Возможно, файл .pdb, содержащий информацию о строке, устарел.

Чтобы это исправить, пересоберите свой проект и убедитесь, что в настройках проекта включено создание файлов .pdb. Для проектов C# это можно настроить на вкладке Сборка, задав для параметра Дополнительно -> Информация об отладке значение полный или только pdb<. /эм>.

person Dirk Vollmar    schedule 26.08.2010
comment
Хорошее предложение, я уже сталкивался с этим. Но я уже пробовал, никакого эффекта. - person Zachary Yates; 26.08.2010

Я думаю, что проблема связана со статическим методом, вызываемым до создания статического объекта. Я исправил проблему в проекте winforms, добавив статический конструктор .

Если я правильно помню, статический конструктор блокирует весь объект во время выполнения.

person Zachary Yates    schedule 26.08.2010
comment
Имеет ли ваш метод доступ к каким-либо статическим полям класса? - person Dirk Vollmar; 26.08.2010
comment
Нет, метод использует только StackFrame и объект Trace, ни один из которых не является статическим полем класса. - person Zachary Yates; 26.08.2010
comment
И что-нибудь еще в вашем классе? Мне просто интересно, почему статический конструктор решил проблему. Это кажется возможным, потому что добавление статического конструктора меняет поведение при инициализации статического класса. - person Dirk Vollmar; 26.08.2010
comment
Я думаю, что это может быть причуда, связанная с созданием StackFrame из статического метода, который неявно вызывает статический конструктор для доступа к методу. Когда вы создали явный статический конструктор, это могло привести к более ранней инициализации класса. Другой эксперимент, который вы можете попробовать, заключается в добавлении другого метода к статическому классу, который не использует StackFrame, а затем вызовите этот метод перед вызовом вашего LogMethodEnter(). - person Dan Bryant; 26.08.2010

Иногда может произойти указание на фигурную скобку/кажущуюся неправильной строку кода. Я думаю, что просто исключение возникло в предыдущей строке кода, а Visual Studio почему-то выделяет следующую строку.

На первый взгляд, программа может не сломаться именно на той строке, где произошло исключение, из-за множества внутренних и внешних факторов.

Извините, я не могу объяснить это очень хорошо.

person cofiem    schedule 26.08.2010
comment
Visual Studio Выделите следующую строку, когда выполнение уже прошло предыдущую строку, но еще не достигло следующей строки. В основном вы увидите это, когда Visual Studio выделит строку зеленым цветом; это означает, что ошибка произошла внутри метода, вызванного предыдущей строкой. - person Pierre-Alain Vigeant; 26.08.2010