Фон
Я согласен, что это не то, что может произойти во время обычного выполнения кода, но я обнаружил его во время отладки и думал, что интересно поделиться.
Я думаю, что это вызвано компилятором JIT, но приветствуем дальнейшие мысли.
Я реплицировал эту проблему, ориентированную на рамки 4.5 и 4.5.1, используя VS2013:
Настройка
Чтобы увидеть это исключение, необходимо включить Common Language Runtime Exceptions
:
DEBUG
> Exceptions...
Я объяснил причину проблемы следующим примером:
using System.Collections.Generic;
using System.Linq;
namespace ConsoleApplication6
{
public class Program
{
static void Main()
{
var myEnum = MyEnum.Good;
var list = new List<MyData>
{
new MyData{ Id = 1, Code = "1"},
new MyData{ Id = 2, Code = "2"},
new MyData{ Id = 3, Code = "3"}
};
// Evaluates to false
if (myEnum == MyEnum.Bad) // BREAK POINT
{
/*
* A first chance exception of type 'System.NullReferenceException' occurred in ConsoleApplication6.exe
Additional information: Object reference not set to an instance of an object.
*/
var x = new MyClass();
MyData result;
//// With this line the 'System.NullReferenceException' gets thrown in the line above:
result = list.FirstOrDefault(r => r.Code == x.Code);
//// But with this line, with 'x' not referenced, the code above runs ok:
//result = list.FirstOrDefault(r => r.Code == "x.Code");
}
}
}
public enum MyEnum
{
Good,
Bad
}
public class MyClass
{
public string Code { get; set; }
}
public class MyData
{
public int Id { get; set; }
public string Code { get; set; }
}
}
Репликация
Поместите точку останова на if (myEnum == MyEnum.Bad)
и запустите код.
Когда точка разрыва попадает, Set Next Statement
(Ctrl + Shift + F10) является открывающей скобкой оператора if
и выполняется до:
Затем запишите первый оператор lamda и комментарий во втором - поэтому экземпляр MyClass
не используется.
Перезапустите процесс (нажав на перерыв, заставив оператор if
и запустив). Вы увидите, что код работает правильно:
Наконец, комментируйте первый оператор lamda и закомментируйте второй - так что используется MyClass
instance . Затем преобразуйте содержимое инструкции if
в новый метод:
using System.Collections.Generic;
using System.Linq;
namespace ConsoleApplication6
{
public class Program
{
static void Main()
{
var myEnum = MyEnum.Good;
var list = new List<MyData>
{
new MyData{ Id = 1, Code = "1"},
new MyData{ Id = 2, Code = "2"},
new MyData{ Id = 3, Code = "3"}
};
// Evaluates to false
if (myEnum == MyEnum.Bad) // BREAK POINT
{
MyMethod(list);
}
}
private static void MyMethod(List<MyData> list)
{
// When the code is in this method, it works fine
var x = new MyClass();
MyData result;
result = list.FirstOrDefault(r => r.Code == x.Code);
}
}
public enum MyEnum
{
Good,
Bad
}
public class MyClass
{
public string Code { get; set; }
}
public class MyData
{
public int Id { get; set; }
public string Code { get; set; }
}
}
Повторите тест, и все работает правильно:
Заключение
Мое предположение заключается в том, что компилятор JIT оптимизировал lamda всегда равным нулю, а еще один оптимизированный код работает до инициализации экземпляра.
Как я уже говорил, это никогда не может произойти в производственном коде, но мне было бы интересно узнать, что происходит.