Когда я выполняю некоторый код, выбрасывается исключение NullReferenceException
со следующим сообщением:
Object reference not set to an instance of an object.
или
В экземпляре объекта не задана ссылка на объект.
Что это значит, и как мне исправить код?
Вы пытаетесь воспользоваться чем-то, что равно null
(или Nothing
в VB.NET). Это означает, что либо вы присвоили это значение, либо вы ничего не присваивали.
Как и любое другое значение, null
может передаваться от объекта к объекту, от метода к методу. Если нечто равно null
в методе "А", вполне может быть, что метод "В" передал это значение в метод "А".
Остальная часть статьи описывает происходящее в деталях и перечисляет распространённые ошибки, которые могут привести к исключению NullReferenceException
.
Если среда выполнения выбрасывает исключение NullReferenceException
, это всегда означает одно: вы пытаетесь воспользоваться ссылкой. И это ссылка не инициализирована (или была инициализирована, но уже не инициализирована).
Это означает, что ссылка равна null
, а вы не сможете вызвать методы через ссылку, равную null
. В простейшем случае:
string foo = null;
foo.ToUpper();
Этот код выбросит исключение NullReferenceException
на второй строке, потому что вы не можете вызвать метод ToUpper()
у ссылки на string
, равной null
.
Как определить источник ошибки? Кроме изучения собственно исключения, которое будет выброшено именно там, где оно произошло, вы можете воспользоваться общими рекомендациями по отладке в Visual Studio: поставьте точки останова в ключевых точках, изучите значения переменных, либо расположив курсор мыши над переменной, либо открыв панели для отладки: Watch, Locals, Autos.
Если вы хотите определить место, где значение ссылки устанавливается или не устанавливается, нажмите правой кнопкой на её имени и выберите "Find All References". Затем вы можете поставить точки останова на каждой найденной строке и запустить приложение в режиме отладки. Каждый раз, когда отладкик остановится на точке останова, вы можете удостовериться, что значение верное.
Следя за ходом выполнения программы, вы придёте к месту, где значение ссылки не должно быть null
, и определите, почему не присвоено верное значение.
Несколько общих примеров, в которых возникает исключение.
ref1.ref2.ref3.member
Если ref1
, ref2
или ref3
равно null
, вы получите NullReferenceException
. Для решения проблемы и определения, что именно равно null
, вы можете переписать выражение более простым способом:
var r1 = ref1;
var r2 = r1.ref2;
var r3 = r2.ref3;
r3.member
Например, в цепочке HttpContext.Current.User.Identity.Name
, значение может отсутствовать и у HttpContext.Current
, и у User
, и у Identity
.
public class Person {
public int Age { get; set; }
}
public class Book {
public Person Author { get; set; }
}
public class Example {
public void Foo() {
Book b1 = new Book();
int authorAge = b1.Author.Age; // Свойство Author не было инициализировано
// нет Person, у которого можно вычислить Age.
}
}
То же верно для вложенных инициализаторов:
Book b1 = new Book { Author = { Age = 45 } };
Несмотря на использование ключевого слова new
, создаётся только экземпляр класса Book
, но экземпляр Person
не создаётся, поэтому свойство Author
остаётся null
.
int[] numbers = null;
int n = numbers[0]; // numbers = null. Нет массива, чтобы получить элемент по индексу
Person[] people = new Person[5];
people[0].Age = 20; // people[0] = null. Массив создаётся, но не
// инициализируется. Нет Person, у которого можно задать Age.
long[][] array = new long[1][];
array[0][0] = 3; // = null, потому что инициализировано только первое измерение.
// Сначала выполните array[0] = new long[2].
Dictionary<string, int> agesForNames = null;
int age = agesForNames["Bob"]; // agesForNames = null.
// Экземпляр словаря не создан.
public class Person {
public string Name { get; set; }
}
var people = new List<Person>();
people.Add(null);
var names = from p in people select p.Name;
string firstName = names.First(); // Исключение бросается здесь, хотя создаётся
// строкой выше. p = null, потому что
// первый добавленный элемент = null.
public class Demo
{
public event EventHandler StateChanged;
protected virtual void OnStateChanged(EventArgs e)
{
StateChanged(this, e); // Здесь бросится исключение, если на
// событие StateChanged никто не подписался
}
}
Если бы в коде ниже у локальных переменных и полей были разные имена, вы бы обнаружили, что поле не было инициализировано:
public class Form1 {
private Customer customer;
private void Form1_Load(object sender, EventArgs e) {
Customer customer = new Customer();
customer.Name = "John";
}
private void Button_Click(object sender, EventArgs e) {
MessageBox.Show(customer.Name);
}
}
Можно избежать проблемы, если использовать префикс для полей:
private Customer _customer;
public partial class Issues_Edit : System.Web.UI.Page
{
protected TestIssue myIssue;
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
// Выполняется только на первой загрузке, но не когда нажата кнопка
myIssue = new TestIssue();
}
}
protected void SaveButton_Click(object sender, EventArgs e)
{
myIssue.Entry = "NullReferenceException здесь!";
}
}
// Если сессионная переменная "FirstName" ещё не была задана,
// то эта строка бросит NullReferenceException.
string firstName = Session["FirstName"].ToString();
Если вы возвращаете пустую модель (или свойство модели) в контроллере, то вью бросит исключение при попытке доступа к ней:
// Controller
public class Restaurant:Controller
{
public ActionResult Search()
{
return View(); // Модель не задана.
}
}
// Razor view
@foreach (var restaurantSearch in Model.RestaurantSearch) // Исключение.
{
}
Способы избежать
null
, пропускать кодЕсли вы ожидаете, что ссылка в некоторых случаях будет равна null
, вы можете явно проверить на это значение перед доступом к членам экземпляра:
void PrintName(Person p) {
if (p != null) {
Console.WriteLine(p.Name);
}
}
null
, использовать значение по умолчаниюМетоды могут возвращать null
, например, если не найден требуемый экземпляр. В этом случае вы можете вернуть значение по умолчанию:
string GetCategory(Book b) {
if (b == null)
return "Unknown";
return b.Category;
}
null
, выбрасывать своё исключениеВы также можете бросать своё исключение, чтобы позже его поймать:
string GetCategory(string bookTitle) {
var book = library.FindBook(bookTitle); // Может вернуть null
if (book == null)
throw new BookNotFoundException(bookTitle); // Ваше исключение
return book.Category;
}
Debug.Assert
для проверки на null
для обнаружения ошибки до бросания исключенияЕсли во время разработки вы знаете, что метод может, но вообще-то не должен возвращать null
, вы можете воспользоваться Debug.Assert
для быстрого обнаружения ошибки:
string GetTitle(int knownBookID) {
// Вы знаете, что метод не должен возвращать null
var book = library.GetBook(knownBookID);
// Исключение будет выброшено сейчас, а не в конце метода.
Debug.Assert(book != null, "Library didn't return a book for known book ID.");
// Остальной код...
return book.Title; // Не выбросит NullReferenceException в режиме отладки.
}
Однако эта проверка не будет работать в релизной сборке, и вы снова получите NullReferenceException
, если book == null
.
GetValueOrDefault()
для Nullable типовDateTime? appointment = null;
Console.WriteLine(appointment.GetValueOrDefault(DateTime.Now));
// Отобразит значение по умолчанию, потому что appointment = null.
appointment = new DateTime(2022, 10, 20);
Console.WriteLine(appointment.GetValueOrDefault(DateTime.Now));
// Отобразит дату, а не значение по умолчанию.
??
[C#] или If()
[VB]Краткая запись для задания значения по умолчанию:
IService CreateService(ILogger log, Int32? frobPowerLevel)
{
var serviceImpl = new MyService(log ?? NullLog.Instance);
serviceImpl.FrobPowerLevel = frobPowerLevel ?? 5;
}
В дополнение к ответу @Discord @Squidward, давайте рассмотрим вопрос с другой стороны.
Если у вас в процессе выполнения программы случился NullReferenceException
при доступе по какой-то ссылке, вы должны прежде всего задать себе важный вопрос:
а имеет ли право эта ссылка иметь значение null
?
Во многих случаях правильным ответом будет «нет», и значит, исправлять придётся истинную причину ошибки, которая находится в другом месте, и произошла раньше.
Пример: если у вас есть такой класс:
class Car
{
Engine engine;
Driver driver;
// ... остаток класса
}
Так вот, мотор у машины быть обязан в любом случае, всегда. А вот водитель может в принципе и не сидеть в машине.
Поэтому если вы видите, что обращение к engine.HorsePower
падает с NullReferenceException
, реальная проблема состоит в том, что вы забыли инициализировать engine
в конструкторе. Поэтому и исправлять ошибку нужно не в точке, где падает, а в том месте, которое реально должно бы обеспечить ненулевое значение engine
.
А вот если вылетает обращение driver.Age
, то здесь уже проблема прямо в точке обращения, вам необходимо сначала проверить, что driver != null
, а потом уж обращаться.
Таким образом: если ваша ссылка в точке обращения не имеет права иметь значение null
, то вы не должны дописывать проверку на null
, тем самым «замазывая» ошибку. Вы должны либо ничего не проверять, а исправить в том месте, где ссылка должна быть инициализирована, либо добавить Debug.Assert
, либо проверку на null
и выброс исключения.
Если же ссылка имеет право быть null
-ом, то в этом случае нужно корректно обработать и этот случай.
Важное замечание: Если вашу функцию вызывает «внешний мир», вы не должны рассчитывать, что вашей функции передадут хорошие, правильные аргументы. Даже если вы требуете, чтобы объект, который вам передан, не был null
-ом, всё равно вам могут передать неправильный объект. Поэтому для функций, доступных внешним модулям, необходимо проверять аргументы на null
сразу же в начале кода, и бросать нужное исключение:
public decimal ComputePrice(Car car)
{
if (car == null)
throw new ArgumentNullException("car");
// ...
Где именно проводить границу между «внутренним» и «внешним» миром, вопрос достаточно нетривиальный. Обычно эта граница есть граница модуля (сборки), или даже той её логической части, которая находится в вашей ответственности. Слишком мелкое дробление ведёт к повторению бессмысленного кода (одна часть программы не доверяет другой и постоянно перепроверяет её). Слишком крупное дробление ведёт к необходимости держать в голове миллионы зависимостей («могу я тут передавать null
или нет?»). Пользуйтесь здравым смыслом и личным опытом.
В последующих версиях языка C# (после C# 7) планируется ввести явное различие между этими двумя случаями. Для тех ссылок, которые не должны по-хорошему содержать null
, планируется такой синтаксис:
class Car
{
Engine! engine; // не может быть null
Driver driver; // может быть null
// ... остаток класса
}
или такой:
class Car
{
Engine engine; // не может быть null
Driver? driver; // может быть null
// ... остаток класса
}
Окончательного решения насчёт нового синтаксиса (да и семантики) пока нет.
В этом случае сам компилятор сможет проконтролировать, что вы забыли проинициализировать значение поля engine
.
Айфон мало держит заряд, разбираемся с проблемой вместе с AppLab
Перевод документов на английский язык: Важность и ключевые аспекты
Требуется получить данные по умолчанию из реестраЦель в том что бы достать Current system local, но не получется так как в реестре по умолчанию "Default"...
Доброго времени сутокНужна ваша помощь, вторые сутки сижу над задачей
Помогите решить задачуНужно создать отдельную сущность (новый тип поста) при активации плагина