Разрешение вопросов, связанных с исключениями: System.NullReferenceException
Исключение NullReferenceException возникает при попытке использовать метод или свойство ссылочного типа (C#, Visual Basic), значение которого — null. Например, вы могли попытаться использовать объект, сначала не используя ключевое слово new (New в Visual Basic), или использовать объект, значение которого было задано равным null (Nothing в Visual Basic).
Содержание этой статьи
Классы, используемые в этой статье
Основные причины исключений NullReferenceException
Поиск источника исключений NullReferenceExceptions во время разработки
Предотвращение исключений NullReferenceException
Обработка исключений NullReferenceException в коде выпуска
Классы, используемые в этой статье
В большинстве примеров в этой статье используется один или оба следующих класса:
public class Automobile
{
public EngineInfo Engine {get; set;}
}
public class EngineInfo
{
public EngineInfo() { }
public EngineInfo(string powerSrc, double engineSize)
{
Power = powerSrc;
Size = engineSize;
}
public double Size { get; set; }
public string Power = null;
}
Public Class Automobile
Public Property Engine As EngineInfo
End Class
Public Class EngineInfo
Public Sub New()
End Sub
Public Sub New(powerSrc As String, engineSize As Double)
Power = powerSrc
Size = engineSize
End Sub
Public Property Size() As Double
Public Power As String = Nothing
End Class
Другие разделы этой статьи
Основные причины исключений NullReferenceException
Любая переменная ссылочного типа может равняться null. Локальные переменные, свойства класса, параметры методов и возвращаемые значения методов могут содержать пустую ссылку. Вызов методов или свойств таких переменных, имеющих значение null, приводит к созданию исключения NullReferenceException. Специальные случаи:
Локальная переменная или поле члена объявлено, но не инициализировано
Свойство или поле имеют значение null
Параметр метода равен null
Возвращаемое значение метода равно null
Объект в коллекции или массиве имеет значение null
Объект не создается из-за условия
Для объекта, передаваемого по ссылке в метод, задано значение null
Локальная переменная или поле члена объявлено, но не инициализировано
Эта простая ошибка чаще всего возникает в коде Visual Basic. За исключением, например, таких случаев, когда объявление переменной передается как выходной параметр, компилятор C# не позволяет использовать локальную ссылочную переменную без ее инициализации. Компилятор Visual Basic создает предупреждение.
В следующем коде C# выделенная строка создает данную ошибку компилятора:
Использование неназначенной локальной переменной 'engine'
В коде Visual Basic выделенная строка создает предупреждение компилятора BC42104:
Переменная 'engine' используется до того, как ей присвоено значение. Во время выполнения может произойти исключение, связанное с пустой ссылкой.
При выполнении этой строки создается исключение NullReferenceException.
public void NullReferencFromUninitializedLocalVariable()
{
EngineInfo engine;
Console.WriteLine(engine.ToString());
}
Public Sub NullReferencFromUninitializedLocalVariable()
Dim engine As EngineInfo
Console.WriteLine(engine.ToString())
End Sub
Записи в этом разделе
Содержание этой статьи
Свойство или поле имеют значение null
Поля и свойства класса автоматически инициализируются с их значением по умолчанию при создании класса. Значение по умолчанию ссылочного типа — null (Nothing в Visual Basic). Вызов методов члена для поля или свойства родительского класса, если значение поля или свойства — null, приводит к созданию исключения NullReferenceException.
В этом примере выделенная строка создает исключение NullReferenceException, так как свойство Engine класса car автоматически инициализируется со значением null.
public void NullReferenceFromProperty()
{
var car = new Automobile();
Console.WriteLine(car.Engine.ToString());
}
Public Sub NullReferenceFromProperty()
Dim car = New Automobile()
Console.WriteLine(car.Engine.ToString())
End Sub
Записи в этом разделе
Содержание этой статьи
Параметр метода равен null
Параметр метода ссылочного типа может равняться null (Nothing в Visual Basic). Вызов методов или свойств члена при нулевом значении параметра вызывает исключение NullReferenceException.
В этом примере выделенная строка создает исключение NullReferenceException, так как BadEngineInfoPassedToMethod вызывает NullReferenceFromMethodParameter с параметром, значение которого равняется null.
public void BadEngineInfoPassedToMethod()
{
EngineInfo eng = null;
NullReferenceFromMethodParameter(eng);
}
public void NullReferenceFromMethodParameter(EngineInfo engine)
{
Console.WriteLine(engine.ToString());
}
Public Sub BadParameterPassedToMethod() As EngineInfo
Dim eng As EngineInfo = Nothing
NullReferenceFromMethodParameter(eng)
End Sub
Public Sub NullReferenceFromMethodParameter(engine As EngineInfo)
Console.WriteLine(engine.ToString())
End Sub
Записи в этом разделе
Содержание этой статьи
Возвращаемое значение метода равно null
Метод, возвращающий ссылочный тип, может возвращать значение null (Nothing в Visual Basic). Вызов методов или свойств возвращаемого ссылочного типа создает исключение NullReferenceException, если значение ссылки — null.
В этом примере выделенная строка создает исключение NullReferenceException, так как вызов BadGetEngineInfo возвращает пустую ссылку в методе NullReferenceFromMethodParameter.
public EngineInfo BadGetEngineInfo()
{
EngineInfo engine = null;
return engine;
}
public void NullReferenceFromMethodReturnValue()
{
var engine = BadGetEngineInfo();
Console.WriteLine(engine.ToString());
}
Public Function BadGetEngineInfo() As EngineInfo
Dim engine As EngineInfo = Nothing
Return engine
End Function
Public Sub NullReferenceFromMethodReturnValue()
Dim engine = BadGetEngineInfo()
Console.WriteLine(engine.ToString())
End Sub
Записи в этом разделе
Содержание этой статьи
Объект в коллекции или массиве имеет значение null
Список или массив ссылочных типов может содержать элемент, значение которого равняется null. Вызов методов или свойств элемента списка, который имеет значение «null», вызывает исключение NullReferenceException.
В этом примере выделенная строка в NullReferenceFromListItem() создает исключение NullReferenceException, так как вызов BadGetCarList возвращает элемент, значение которого — null.
public Automobile[] BadGetCarList()
{
var autos = new Automobile[10];
for (int i = 0; i autos.Length; i++)
{
if (i != 6)
{
autos[i] = new Automobile();
}
}
return autos;
}
public void NullReferenceFromListItem()
{
var cars = BadGetCarList();
foreach (Automobile car in cars)
{
Console.WriteLine(car.ToString());
}
}
Public Function BadGetCarList() As Automobile()
Dim autos = New Automobile(10) {}
For i As Integer = 0 To 9
If i <> 6 Then
autos(i) = New Automobile()
End If
Next
Return autos
End Function
Public Sub NullReferenceFromListItem()
Dim cars = BadGetCarList()
For Each car As Automobile In cars
Console.WriteLine(car.ToString())
Next
End Sub
Записи в этом разделе
Содержание этой статьи
Объект не создается из-за условия
Если ссылочный тип инициализируется в условном блоке, объект не создается, если условие не выполнено.
В этом примере выделенная строка в NullReferenceFromConditionalCreation создает исключение NullReferenceException, так как она инициализирует переменную engine только в том случае, если метод DetermineTheCondition() возвращает значение true.
public bool DetermineTheCondition()
{
return false;
}
public void NullReferenceFromConditionalCreation()
{
EngineInfo engine = null;
var condition = DetermineTheCondition();
if (condition)
{
engine = new EngineInfo();
engine.Power = "Diesel";
engine.Size = 2.4;
}
Console.WriteLine(engine.Size);
}
Public Function DetermineTheCondition() As Boolean
Return False
End Function
Public Sub NullReferenceFromConditionalCreation()
Dim engine As EngineInfo = Nothing
Dim condition = DetermineTheCondition()
If condition Then
engine = New EngineInfo()
engine.Power = "Diesel"
engine.Size = 2.4
End If
Console.WriteLine(engine.Size)
End Sub
Записи в этом разделе
Содержание этой статьи
Свойство объекта, передаваемого методу, задано равным null
При передаче в метод объекта как параметра по значению (не используя ключевое слово ref или out в C# либо ByRef в Visual Basic) метод не может изменить ячейку памяти для параметра (на которую указывает параметр), однако может изменить свойства объекта.
В этом примере метод NullPropertyReferenceFromPassToMethod создает объект Automobile и инициализирует свойство Engine. Затем он вызывает BadSwapCarEngine, передавая новый объект в виде параметра. BadSwapCarEngine задает свойство Engine равным null, что вызывает создание выделенной строкой в NullPropertyReferenceFromPassToMethod исключения NullReferenceException.
public void BadSwapCarEngine(Automobile car)
{
car.Engine = null;
}
public void (Automobile car)
{
car.Engine = new EngineInfo("GAS", 1.5);
BadSwapCarEngine(car);
Console.WriteLine(car.Engine.ToString());
}
Public Sub BadSwapCarEngine(car As Automobile)
car.Engine = Nothing
End Sub
Public Sub NullPropertyReferenceFromPassToMethod()
Dim car As New Automobile()
car.Engine = New EngineInfo("GAS", 1.5)
BadSwapCarEngine(car)
Console.WriteLine(car.Engine.ToString())
End Sub
Записи в этом разделе
Содержание этой статьи
Для объекта, передаваемого по ссылке в метод, задано значение null
При передаче в метод ссылочного типа как параметра по ссылке (используя ключевое слово ref или out в C# или ByRef в Visual Basic) можно изменить расположение памяти, на которое указывает параметр.
При передаче в метод ссылочного типа по ссылке метод может задать для ссылочного типа значение null (ключевое слово Nothing в Visual Basic).
В этом примере выделенная строка в NullReferenceFromPassToMethodByRef создает исключение NullReferenceException, так как вызов метода BadEngineSwapByRef задает для переменной stockEngine значение null.
public void BadEngineSwapByRef(ref EngineInfo engine)
{
engine = null;
}
public void NullReferenceFromPassToMethodByRef()
{
var stockEngine = new EngineInfo();
stockEngine.Power = "Gas";
stockEngine.Size = 7.0;
BadSwapEngineByRef(ref stockEngine);
Console.WriteLine(stockEngine.ToString());
}
Public Sub BadSwapEngineByRef(ByRef engine As EngineInfo)
engine = Nothing
End Sub
Public Sub NullReferenceFromPassToMethodByRef()
Dim formatStr = "The stock engine has been replaced by a {0} liter {} engine"
Dim stockEngine = New EngineInfo()
stockEngine.Power = "Gas"
stockEngine.Size = 7.0
BadSwapEngineByRef(stockEngine)
Console.WriteLine(stockEngine.ToString())
End Sub
Записи в этом разделе
Содержание этой статьи
Поиск источника исключения NullReferenceException во время разработки
Использование подсказок по данными, окна локальных переменных и окон контрольных значений для просмотра значений переменных
Обход стека вызовов для поиска вызовов, в которых ссылочные переменные не инициализированы или заданы равными null
Настройка условных точек основа для прекращения отладки при обнаружении объектов, имеющих значение null (Nothing в Visual Basic)
Использование подсказок по данными, окна локальных переменных и окон контрольных значений для просмотра значений переменных
Наведите указатель мыши на имя переменной, чтобы просмотреть это значение в подсказке по данным. Если переменная ссылается на объект или коллекцию, можно развернуть тип данных для анализа его свойств или элементов.
Откройте окно Локальные для анализа активных переменных в текущем контексте.
В окне контрольных значений можно просмотреть, как изменяется переменная при прохождении кода.
Записи в этом разделе
Содержание этой статьи
Обход стека вызовов для поиска вызовов, в которых ссылочные переменные не инициализированы или заданы равными null
Окно стека вызовов Visual Studio содержит список имен методов, которые не были выполнены из-за остановки отладчика в точке останова или исключении. Для изменения контекста выполнения для метода и анализа его переменных можно выбрать имя в окне Стек вызовов и нажать кнопку Перейти к кадру.
Записи в этом разделе
Содержание этой статьи
Настройка условных точек основа для прекращения отладки при обнаружении объектов, имеющих значение null (Nothing в Visual Basic)
Можно задать условную точку останова, чтобы прерывать выполнение, если переменная имеет значение null. Условные точки останова могут быть полезны, когда пустая ссылка не возникает слишком часто, например, когда элемент в коллекции принимает значение null только периодически. Еще одним преимуществом использования условных точек останова является то, что они позволяют выполнять отладку проблемы до ее передачи в конкретную подпрограмму обработки.
Записи в этом разделе
Содержание этой статьи
Предотвращение исключений NullReferenceException
Использование оператор Debug.Assert для проверки инварианта
Полная инициализация ссылочных типов
Использование оператор Debug.Assert для проверки инварианта
Инвариант представляет собой условие, в истинности которого вы уверены. Оператор Debug.Assert (System.Diagnostics) вызывается только из сборок отладки приложения, но не из кода выпуска. Если инвариантное условие не соблюдается, отладчик прерывает выполнение кода в операторе Assert и выводит диалоговое окно. Debug.Assert обеспечивает проверку того, что условие не изменялось во время разработки приложения. Утверждение также уведомляет других пользователей, читающих ваш код, о том, что условие должно всегда иметь значение true.
Например, метод MakeEngineFaster предполагает, что его параметр engine никогда не будет иметь значение null, так как известно, что единственный вызывающий его метод (TheOnlyCallerOfMakeEngineFaster) полностью инициализирует EngineInfo. Оператор assert в методе MakeEngineFaster документирует предположение и выполняет проверку его соблюдения.
Если разработчик добавляет новый вызывающий метод (BadNewCallerOfMakeEngineFaster), который не инициализирует параметр, то срабатывает оператор assert.
private void TheOnlyCallerOfMakeEngineFaster()
{
var engine = new EngineInfo();
engine.Power = "GAS";
engine.Size = 1.5;
MakeEngineFaster(engine);
}
private void MakeEngineFaster(EngineInfo engine)
{
System.Diagnostics.Debug.Assert(engine != null, "Assert: engine != null");
engine.Size *= 2;
Console.WriteLine("The engine is twice as fast");
}
private void BadNewCallerOfMakeEngineFaster()
{
EngineInfo engine = null;
MakeEngineFaster(engine);
}
Public Sub TheOnlyCallerOfMakeEngineFaster()
Dim engine As New EngineInfo
engine.Power = "GAS"
engine.Size = 1.5
MakeEngineFaster(engine)
End Sub
Private Sub MakeEngineFaster(engine As EngineInfo)
System.Diagnostics.Debug.Assert(engine IsNot Nothing, "Assert: engine IsNot Nothing")
engine.Size = engine.Size * 2
Console.WriteLine("The engine is twice as fast")
End Sub
Public Sub BadNewCallerOfMakeEngineFaster()
Dim engine As EngineInfo = Nothing
MakeEngineFaster(engine)
End Sub
Записи в этом разделе
Содержание этой статьи
Полная инициализация ссылочных типов
Чтобы избежать массового возникновения исключений NullReferenceException, выполните полную инициализацию ссылочных типов как можно скорее после их создания.
Добавление полной инициализации в собственные классы
Если вы можете управлять классом, который создает исключение NullReferenceException, рекомендуется выполнять полную инициализацию объекта в конструкторе типа. Например, ниже приведена исправленная версия примеров классов, которая гарантирует полную инициализацию:
public class Automobile
{
public EngineInfo Engine { get; set; }
public Automobile()
{
this.Engine = new EngineInfo();
}
public Automobile(string powerSrc, double engineSize)
{
this.Engine = new EngineInfo(powerSrc, engineSize);
}
}
public class EngineInfo
{
public double Size {get; set;}
public string Power {get; set;}
public EngineInfo()
{
// the base engine
this.Power = "GAS";
this.Size = 1.5;
}
public EngineInfo(string powerSrc, double engineSize)
{
this.Power = powerSrc;
this.Size = engineSize;
}
}
Public Class Automobile
Public Property Engine As EngineInfo
Public Sub New()
Me.Engine = New EngineInfo()
End Sub
Public Sub New(powerSrc As String, engineSize As Double)
Me.Engine = New EngineInfo(powerSrc, engineSize)
End Sub
End Class
Public Class BaseEngineInfo
Public Sub New()
' the base engine
Me.Power = "GAS"
Me.Size = 1.5
End Sub
Public Sub New(powerSrc As String, engineSize As Double)
Power = powerSrc
Size = engineSize
End Sub
Public Property Size() As Double
Public Power As String = String.Empty
End Class
Примечание
Использование отложенной инициализации для больших или редко используемых свойств
Чтобы сократить использование памяти для класса и повысить производительность, рекомендуется использовать отложенную инициализацию свойств ссылочных типов.См. раздел Отложенная инициализация.
Обработка исключений NullReferenceException в коде выпуска
Проверка значений null (Nothing в Visual Basic) перед использованием ссылочного типа
Использование конструкции try – catch – finally (Try – Catch – Finally в Visual Basic) для обработки исключения
Лучше избегать исключения NullReferenceException, чем обрабатывать его после возникновения. Обработка исключений может затруднять обслуживание и понимание вашего кода, а иногда даже приводить к другим ошибкам. Исключение NullReferenceException часто является неисправимой ошибкой. В этом случае, возможно, будет лучше позволить исключению остановить приложение.
Однако существует много ситуаций, в которых процесс обработки ошибки может оказаться полезен:
Приложение может пропускать объекты, которые имеют значение null. Например, если приложение получает и обрабатывает записи в базе данных, можно игнорировать некоторое число неверных записей, которые вызывают возникновение объектов со значением null. Возможно, все, что потребуется, — записать неверные данные в файл журнала или интерфейс пользователя приложения.
Можно попытаться исправить исключение. Например, вызов веб-службы, который возвращает ссылочный тип, может вернуть значение null в случае разрыва или истечения времени ожидания подключения. Можно попытаться восстановить подключение и повторить вызов.
Можно восстановить приложение до допустимого состояния. Например, вы могли выполнять задачу, состоящую из нескольких шагов, которая требует сохранения информации в хранилище данных, перед вызовом метода, который создает исключение NullReferenceException. Если неинициализированный объект повредит запись данных, можно будет удалить предыдущие данные перед закрытием приложения.
Требуется сообщить об исключении. Например, если ошибка была вызвана неправильным действием пользователя приложения, можно создать сообщение, которое поможет ему предоставить правильные сведения. Кроме того, можно зарегистрировать сведения об ошибке, которые помогут устранить проблему. Некоторые платформы, например, ASP.NET, содержат высокогоуровневый обработчик исключений, который перехватывает все ошибки, чтобы приложения никогда аварийно не завершало работу; в этом случае только благодаря регистрации исключения вы сможете узнать о наличии ошибки.
Ниже рассмотрены два способа обработки исключения NullReferenceException в коде выпуска.
Проверка значений null (Nothing в Visual Basic) перед использованием ссылочного типа
Использование явной проверки значений null перед использованием объекта позволяет избежать потери производительности конструкций try-catch-finally. Тем не менее, вы по-прежнему должны определять и реализовывать действие в ответ на появление неинициализированного объекта.
В этом примере CheckForNullReferenceFromMethodReturnValue проверяет возвращаемое значение метода BadGetEngineInfo. Если объект не имеет значение null, он используется; в противном случае метод сообщает об ошибке.
public EngineInfo BadGetEngineInfo()
{
EngineInfo engine = null;
return engine;
}
public void CheckForNullReferenceFromMethodReturnValue()
{
var engine = BadGetEngineInfo();
if(engine != null)
{
// modify the info
engine.Power = "DIESEL";
engine.Size = 2.4;
}
else
{
// report the error
Console.WriteLine("BadGetEngine returned null")
}
}
public EngineInfo BadGetEngineInfo()
{
EngineInfo engine = null;
return engine;
}
Public Sub CheckForNullReferenceFromMethodReturnValue()
Dim engine = BadGetEngineInfo()
If (engine IsNot Nothing) Then
' modify the info
engine.Power = "DIESEL"
engine.Size = 2.4
Else
' report the error
Console.WriteLine("BadGetEngineInfo returned Nothing")
End If
End Sub
Записи в этом разделе
Содержание этой статьи
Использование конструкции try-catch-finally (Try-Catch-Finally в Visual Basic) для обработки исключения
Использование встроенных конструкций обработки исключений (try, catch, finally в C#, Try, Catch, Finally в Visual Basic) предоставляет дополнительные возможности обработки исключений NullReferenceException, помимо проверки того, что используемый объект не имеет значение null.
В этом примере метод CatchNullReferenceFromMethodCall использует два оператора assert для проверки предположения о том, что его параметр содержит полный "автомобиль" (automobile), включая "двигатель" (engine). В блоке try выделенная строка создает исключение NullReferenceException, так как вызов метода RarelyBadEngineSwap может разрушить свойство Engine автомобиля. Блок catch перехватывает исключение, записывает сведения об исключении в файл и сообщает об ошибке пользователю. В блоке finally метод следит за тем, что состояние "машины" (car) было не хуже, чем в начале метода.
public void RarelyBadSwapCarEngine(Automobile car)
{
if ((new Random()).Next() == 42)
{
car.Engine = null;
}
else
{
car.Engine = new EngineInfo("DIESEL", 2.4);
}
}
public void CatchNullReferenceFromMethodCall(Automobile car)
{
System.Diagnostics.Debug.Assert(car != null, "Assert: car != null");
System.Diagnostics.Debug.Assert(car.Engine != null, "Assert: car.Engine != null");
// save current engine properties in case they're needed
var enginePowerBefore = car.Engine.Power;
var engineSizeBefore = car.Engine.Size;
try
{
RarelyBadSwapCarEngine(car);
var msg = "Swap succeeded. New engine power source: {0} size {1}";
Console.WriteLine(msg, car.Engine.Power, car.Engine.Size);
}
catch(NullReferenceException nullRefEx)
{
// write exception info to log file
LogException(nullRefEx);
// notify the user
Console.WriteLine("Engine swap failed. Please call your customer rep.");
}
finally
{
if(car.Engine == null)
{
car.Engine = new EngineInfo(enginePowerBefore, engineSizeBefore);
}
}
}
Public Sub RarelyBadSwapCarEngine(car As Automobile)
If (New Random()).Next = 42 Then
car.Engine = Nothing
Else
car.Engine = New EngineInfo("DIESEL", 2.4)
End If
End Sub
Public Sub CatchNullReferenceFromMethodCall(car As Automobile)
System.Diagnostics.Debug.Assert(car IsNot Nothing)
System.Diagnostics.Debug.Assert(car.Engine IsNot Nothing)
' save current engine properties in case they're needed
Dim powerBefore = car.Engine.Power
Dim sizeBefore = car.Engine.Size
Try
RarelyBadSwapCarEngine(car)
Dim msg = "Swap succeeded. New engine power source: {0} size {1}"
Console.WriteLine(msg, car.Engine.Power, car.Engine.Size)
Catch nullRefEx As NullReferenceException
' write exception info to log file
LogException(nullRefEx)
' notify user
Console.WriteLine("Engine swap failed. Please call your customer rep.")
Finally
If car.Engine Is Nothing Then car.Engine = New EngineInfo(powerBefore, sizeBefore)
End Try
End Sub
Записи в этом разделе
Содержание этой статьи
Связанные статьи
Правила разработки исключений (Разработка на .NET Framework)
Обработка и создание исключений (Основные сведения о приложениях .NET Framework)
Исключения в управляемых потоках (Руководство по разработке .NET Framework)
Исключения и обработка исключений (Руководство по программированию на C#)
Операторы обработки исключений (Справочник по C#)
Оператор Try...Catch...Finally (Visual Basic)
Обработка исключений (Библиотека параллельных задач)
Обработка исключений (Отладка)
Пошаговое руководство. Обработка исключения параллелизма (Доступ к данным в Visual Studio)
Обработка исключений в сетевых приложениях (XAML) (Windows)
Содержание этой статьи