Що таке NullReferenceException, і як це виправити?


1875

У мене є якийсь код, і коли він виконується, він кидає NullReferenceException, кажучи:

Посилання на об'єкт не встановлено для примірника об'єкта.

Що це означає, і що я можу зробити, щоб виправити цю помилку?


Помічник винятків у VS 2017 буде більш корисним для діагностики причини цього винятку - blogs.msdn.microsoft.com/visualstudio/2016/11/28/… у розділі Новий помічник винятку .
Зев Шпіц

Шановні майбутні відвідувачі, відповіді на це питання однаково стосуються ArgumentNullException . Якщо ваше запитання було закрито як дублікат цього питання, і у вас виникає ANE, будь ласка, дотримуйтесь вказівок у відповідях на налагодження та виправте свою проблему.

@will ANE має відбутися лише в тому випадку, якщо в якості параметра буде передано нуль. Чи можете ви навести приклад, якщо питання про ANE закрито як дублікат цього?
Джон Сондерс

Він з'явився на Meta, але я повинен був би перекопати посилання. Щодо цього коментаря, ANE - це просто NRE, але хтось додав попереджувальну перевірку, і ви, принаймні, точно знаєте, що є нульовим (ім'я аргументу надано), тому діагностувати трохи простіше, ніж прямо NRE.

Відповіді:


2415

У чому причина?

Нижня лінія

Ви намагаєтесь використовувати щось, що є null(або Nothingу VB.NET). Це означає, що ви або встановлюєте його null, або ви ніколи не встановлюєте його ні на що.

Як і все інше, nullйого пропускають навколо. Якщо це null в методі "А", то може бути, що метод "В" перейшов null до методу "А".

null може мати різні значення:

    1. Об'єктні змінні, які є неініціалізованими і, отже, вказують ні на що. У цьому випадку, якщо ви отримуєте доступ до властивостей або методів таких об'єктів, це викликає a NullReferenceException.
    1. Розробник навмисно використовує, nullщоб вказати, що немає значущого значення. Зауважте, що C # має поняття змінних типів даних для змінних (наприклад, таблиці баз даних можуть мати нульові поля) - ви можете призначити nullїм, щоб вказати, що в ньому не зберігається значення, наприклад, int? a = null;коли знак питання вказує, що дозволено зберігати null в змінна a. Ви можете перевірити це або з, if (a.HasValue) {...}або з if (a==null) {...}. Зменшувані змінні, як aцей приклад, дозволяють отримати доступ до значення за допомогою a.Valueявного або так само, як звичайного, через a.
      Зверніть увагу , що доступ до нього через a.ValueВидає InvalidOperationExceptionзамість NullReferenceExceptionякщо aISnull- ви повинні заздалегідь зробити перевірку, тобто якщо у вас є інша змінна-нульова змінна, int b;то вам слід виконувати завдання типу if (a.HasValue) { b = a.Value; }або коротше if (a != null) { b = a; }.

В решті цієї статті детальніше йдеться про помилки, які часто роблять багато програмістів, які можуть призвести до появи NullReferenceException.

Більш конкретно

runtimeМетанні NullReferenceException завжди означає те ж саме: ви намагаєтеся використовувати посилання, і посилання не ініціалізується (або він був коли - то инициализируется, але не більше не инициализирован).

Це означає, що посилання є null, і ви не можете отримати доступ до членів (наприклад, методів) через nullпосилання. Найпростіший випадок:

string foo = null;
foo.ToUpper();

Це призведе NullReferenceExceptionдо другого рядка, оскільки ви не можете викликати метод екземпляра ToUpper()на stringпосилання, що вказує на null.

Налагодження

Як знайти джерело NullReferenceException? Окрім перегляду самого винятку, яке буде кинуто саме в тому місці, де воно відбувається, застосовуються загальні правила налагодження у Visual Studio: розміщуйте стратегічні точки перерви та перевіряйте ваші змінні , або наведення курсором миші на їх імена, відкриваючи ( Швидко) Переглядайте вікно або використовуючи різні налагоджувальні панелі, такі як Locals та Autos.

Якщо ви хочете дізнатися, де посилання знаходиться чи не встановлено, клацніть правою кнопкою миші його ім’я та виберіть "Знайти всі посилання". Потім ви можете розмістити точку перерви у кожному знайденому місці та запустити свою програму із доданим налагоджувачем. Кожен раз, коли налагоджувач перерветься на таку точку розриву, вам потрібно визначити, чи очікуєте ви, що посилання буде недійсним, перевірити змінну та переконатися, що вона вказує на екземпляр, коли ви очікуєте її.

Слідуючи потік програми таким чином, ви можете знайти місце, де екземпляр не повинен бути нульовим, і чому він неправильно встановлений.

Приклади

Деякі загальні сценарії, коли виняток можна кинути:

Родовий

ref1.ref2.ref3.member

Якщо ref1 або ref2 або ref3 недійсні, ви отримаєте a NullReferenceException. Якщо ви хочете вирішити проблему, то з’ясуйте, який з них є нульовим, переписавши вираз у його простіший еквівалент:

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; // You never initialized the Author property.
                                       // there is no Person to get an Age from.
    }
}

Якщо ви хочете уникнути нульової посилання на дочірню (Person), ви можете ініціалізувати її в конструкторі батьківського об'єкта (Book).

Вкладені ініціалізатори об'єктів

Те ж стосується і вкладених ініціалізаторів об'єктів:

Book b1 = new Book 
{ 
   Author = { Age = 45 } 
};

Це означає

Book b1 = new Book();
b1.Author.Age = 45;

Хоча newключове слово використовується, воно створює лише новий екземпляр Book, але не новий екземпляр Person, тому Authorвластивість все ще є null.

Вкладені ініціалізатори збору

public class Person 
{
    public ICollection<Book> Books { get; set; }
}
public class Book 
{
    public string Title { get; set; }
}

Вкладені колекції Initializersведуть себе однаково:

Person p1 = new Person 
{
    Books = {
         new Book { Title = "Title1" },
         new Book { Title = "Title2" },
    }
};

Це означає

Person p1 = new Person();
p1.Books.Add(new Book { Title = "Title1" });
p1.Books.Add(new Book { Title = "Title2" });

new PersonТільки створює екземпляр Person, але Booksколекція до цих пір null. InitializerСинтаксис колекції не створює колекцію для p1.Books, вона лише перекладається на p1.Books.Add(...)твердження.

Масив

int[] numbers = null;
int n = numbers[0]; // numbers is null. There is no array to index.

Елементи масиву

Person[] people = new Person[5];
people[0].Age = 20 // people[0] is null. The array was allocated but not
                   // initialized. There is no Person to set the Age for.

Зазубрені масиви

long[][] array = new long[1][];
array[0][0] = 3; // is null because only the first dimension is yet initialized.
                 // Use array[0] = new long[2]; first.

Збірник / Список / Словник

Dictionary<string, int> agesForNames = null;
int age = agesForNames["Bob"]; // agesForNames is null.
                               // There is no Dictionary to perform the lookup.

Змінна діапазон (непряма / відкладена)

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(); // Exception is thrown here, but actually occurs
                                  // on the line above.  "p" is null because the
                                  // first element we added to the list is null.

Події

public class Demo
{
    public event EventHandler StateChanged;

    protected virtual void OnStateChanged(EventArgs e)
    {        
        StateChanged(this, e); // Exception is thrown here 
                               // if no event handlers have been attached
                               // to StateChanged event
    }
}

###Bad Naming Conventions:

If you named fields differently from locals, you might have realized that you never initialized the field. 

публічний клас Form1 {приватний клієнт-замовник;

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;

Життєвий цикл сторінки ASP.NET:

public partial class Issues_Edit : System.Web.UI.Page
{
    protected TestIssue myIssue;

    protected void Page_Load(object sender, EventArgs e)
    {
        if (!IsPostBack)
        {
             // Only called on first load, not when button clicked
             myIssue = new TestIssue(); 
        }
    }

    protected void SaveButton_Click(object sender, EventArgs e)
    {
        myIssue.Entry = "NullReferenceException here!";
    }
}

Значення сеансу ASP.NET

// if the "FirstName" session value has not yet been set,
// then this line will throw a NullReferenceException
string firstName = Session["FirstName"].ToString();

Моделі порожнього перегляду ASP.NET MVC

Якщо виняток виникає при посиланні на властивість @Modelу ASP.NET MVC View, вам потрібно зрозуміти, що Modelвстановлюється у вашому методі дій під returnчас перегляду. Коли ви повертаєте порожню модель (або властивість моделі) з контролера, виняток виникає, коли представлення доступу до неї:

// Controller
public class Restaurant:Controller
{
    public ActionResult Search()
    {
        return View();  // Forgot the provide a Model here.
    }
}

// Razor view 
@foreach (var restaurantSearch in Model.RestaurantSearch)  // Throws.
{
}

<p>@Model.somePropertyName</p> <!-- Also throws -->

Порядок створення та події управління WPF Control

WPFелементи керування створюються під час виклику InitializeComponentв порядку, у якому вони відображаються у візуальному дереві. A NullReferenceExceptionбуде піднято у випадку ранньо-створених елементів керування з обробниками подій тощо, що під час передачі InitializeComponentпосилань на пізно створені елементи управління.

Наприклад :

<Grid>
    <!-- Combobox declared first -->
    <ComboBox Name="comboBox1" 
              Margin="10"
              SelectedIndex="0" 
              SelectionChanged="comboBox1_SelectionChanged">
       <ComboBoxItem Content="Item 1" />
       <ComboBoxItem Content="Item 2" />
       <ComboBoxItem Content="Item 3" />
    </ComboBox>

    <!-- Label declared later -->
    <Label Name="label1" 
           Content="Label"
           Margin="10" />
</Grid>

Тут comboBox1створено раніше label1. Якщо буде comboBox1_SelectionChangedспроба посилання на label1, вона ще не буде створена.

private void comboBox1_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
    label1.Content = comboBox1.SelectedIndex.ToString(); // NullReference here!!
}

Зміна порядку декларацій у XAML(тобто, перерахування label1раніше comboBox1, ігнорування питань філософії дизайну, NullReferenceExceptionтут принаймні вирішить .

У ролях з as

var myThing = someObject as Thing;

Це не кидає InvalidCastExceptionа, але повертає a, nullколи кастинг виходить з ладу (і коли someObjectвін сам є нульовим). Тож пам’ятайте про це.

LINQ FirstOrDefault()іSingleOrDefault()

Прості версії First()і Single()викидають винятки, коли нічого немає. Версії "OrDefault" повертають нуль у цьому випадку. Тож пам’ятайте про це.

для кожного

foreachкидає, коли ви намагаєтесь повторити колекцію нуля. Зазвичай викликається несподіваним nullрезультатом методів, які повертають колекції.

List<int> list = null;    
foreach(var v in list) { } // exception

Більш реалістичний приклад - виберіть вузли з XML-документа. Закине, якщо вузли не знайдено, але початкова налагодження показує, що всі властивості дійсні:

foreach (var node in myData.MyXml.DocumentNode.SelectNodes("//Data"))

Способи уникнути

Явно перевіряють 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);  // This may return null
    if (book == null)
        throw new BookNotFoundException(bookTitle);  // Your custom exception
    return book.Category;
}

Використовуйте, Debug.Assertякщо значення ніколи не повинно бути null, щоб вирішити проблему раніше, ніж відбувається виняток.

Коли ви знаєте під час розробки, що метод, можливо, може, але ніколи не повинен повертатися null, ви можете використовувати, Debug.Assert()щоб перервати якомога швидше, коли це відбувається:

string GetTitle(int knownBookID) 
{
    // You know this should never return null.
    var book = library.GetBook(knownBookID);  

    // Exception will occur on the next line instead of at the end of this method.
    Debug.Assert(book != null, "Library didn't return a book for known book ID.");

    // Some other code

    return book.Title; // Will never throw NullReferenceException in Debug mode.
}

Хоча ця перевірка не закінчиться у вашій версії версії , вона призведе до того, що вона NullReferenceExceptionповторно кинеться book == nullпід час виконання у режимі випуску.

Використовуйте GetValueOrDefault()для nullableтипів значень, щоб вказати значення за замовчуванням, коли вони є null.

DateTime? appointment = null;
Console.WriteLine(appointment.GetValueOrDefault(DateTime.Now));
// Will display the default value provided (DateTime.Now), because appointment is null.

appointment = new DateTime(2022, 10, 20);
Console.WriteLine(appointment.GetValueOrDefault(DateTime.Now));
// Will display the appointment date, not the default

Скористайтеся оператором нульового згортання: ??[C #] або If()[VB].

Скорочення до значення за замовчуванням, коли nullвиникає:

IService CreateService(ILogger log, Int32? frobPowerLevel)
{
   var serviceImpl = new MyService(log ?? NullLog.Instance);

   // Note that the above "GetValueOrDefault()" can also be rewritten to use
   // the coalesce operator:
   serviceImpl.FrobPowerLevel = frobPowerLevel ?? 5;
}

Використовуйте оператор null condition: ?.або ?[x]для масивів (доступний у C # 6 та VB.NET 14):

Це також іноді називають оператором безпечної навігації або Елвісом (за його формою). Якщо вираз з лівого боку оператора є нульовим, то права частина не буде оцінена, а нуль повернеться замість цього. Це означає такі випадки:

var title = person.Title.ToUpper();

Якщо особа не має титулу, це призведе до виключення, оскільки вона намагається зателефонувати ToUpperна властивість з нульовим значенням.

Внизу C# 5та нижче, це може бути захищено за допомогою:

var title = person.Title == null ? null : person.Title.ToUpper();

Тепер змінна назва буде нульовою замість того, щоб викидати виняток. C # 6 вводить для цього коротший синтаксис:

var title = person.Title?.ToUpper();

Це призведе до появи змінної заголовка null, а виклик до ToUpperне робиться, якщо person.Titleє null.

Звичайно, вам все одно доведеться перевірити titleнаявність null або використовувати оператор умови null разом з оператором coleescing null ( ??) для надання значення за замовчуванням:

// regular null check
int titleLength = 0;
if (title != null)
    titleLength = title.Length; // If title is null, this would throw NullReferenceException

// combining the `?` and the `??` operator
int titleLength = title?.Length ?? 0;

Так само для масивів можна використовувати ?[i]наступне:

int[] myIntArray=null;
var i=5;
int? elem = myIntArray?[i];
if (!elem.HasValue) Console.WriteLine("No value");

Це зробить наступне: Якщо myIntArraynull, вираз повертає null, і ви можете сміливо перевірити його. Якщо він містить масив, він буде робити те саме, що: elem = myIntArray[i];і повертає i<sup>th</sup>елемент.

Використовуйте нульовий контекст (доступний у C # 8):

Введені C# 8там нульові типи контексту та нульові еталонні типи виконують статичний аналіз на змінні та надають попередження компілятора, якщо значення потенційно може бути нульовим або встановлено на нуль. Нульові типи посилань дозволяють явним чином дозволити типам бути недійсними.

Контекст анотації, що міняється, і контекст попереджувального попередження можуть бути встановлені для проекту, використовуючи Nullableелемент у вашому csprojфайлі. Цей елемент налаштовує, як компілятор інтерпретує нульовість типів та які попередження створюються. Дійсні налаштування:

  • enable: включений контекст анулювання, що міняється. Увімкнено контекст попереджувального попередження. Змінні еталонного типу, наприклад, рядок, не змінюються. Усі попередження про нікчемність увімкнено.
  • відключити: контекст анулювання, що міняється, вимкнено. Контекст попереджувального попередження відключений. Змінні еталонного типу не враховують, як і попередні версії C #. Усі попередження про нівелювання вимкнено.
  • safeonly: Увімкнено контекст анулювання, що зводиться до нуля. Контекст попереджувального попередження є безпечним. Змінні еталонного типу є незмінні. Усі попередження про зменшення безпеки увімкнено.
  • застереження: контекст анулювання, що міняється, вимкнено. Увімкнено контекст попереджувального попередження. Змінні еталонного типу не враховують. Усі попередження про нікчемність увімкнено.
  • safeonly предупреждения: контекст анулювання, що міняється, вимкнено. Контекст попереджувального попередження є безпечним. Змінні еталонного типу не враховують. Усі попередження про зменшення безпеки увімкнено.

Нульовий тип посилання відмічається, використовуючи той самий синтаксис, що і типи нульових значень: a ?додається до типу змінної.

Спеціальні методи налагодження та фіксації нульових дерефів в ітераторах

C#підтримує "блоки ітераторів" (які називаються "генераторами" в деяких інших популярних мовах). Винятки з нульової відношення можуть бути особливо складними для налагодження в ітераторних блоках через відкладене виконання:

public IEnumerable<Frob> GetFrobs(FrobFactory f, int count)
{
    for (int i = 0; i < count; ++i)
    yield return f.MakeFrob();
}
...
FrobFactory factory = whatever;
IEnumerable<Frobs> frobs = GetFrobs();
...
foreach(Frob frob in frobs) { ... }

Якщо whateverрезультати в nullтоді MakeFrobбуде кинути. Тепер ви можете подумати, що правильно це зробити:

// DON'T DO THIS
public IEnumerable<Frob> GetFrobs(FrobFactory f, int count)
{
   if (f == null) 
      throw new ArgumentNullException("f", "factory must not be null");
   for (int i = 0; i < count; ++i)
      yield return f.MakeFrob();
}

Чому це неправильно? Тому що блок ітераторів насправді не працює до foreach! Заклик GetFrobsпросто повертає об'єкт, який при ітерації запустить блок ітератора.

Написавши подібну нульову перевірку, ви запобігаєте нульовій відміні, але ви переміщуєте виняток з нульовим аргументом до точки ітерації , а не до точки виклику , і це дуже заплутано для налагодження .

Правильне виправлення:

// DO THIS
public IEnumerable<Frob> GetFrobs(FrobFactory f, int count)
{
   // No yields in a public method that throws!
   if (f == null) 
       throw new ArgumentNullException("f", "factory must not be null");
   return GetFrobsForReal(f, count);
}
private IEnumerable<Frob> GetFrobsForReal(FrobFactory f, int count)
{
   // Yields in a private method
   Debug.Assert(f != null);
   for (int i = 0; i < count; ++i)
        yield return f.MakeFrob();
}

Тобто, зробіть приватний хелперний метод, який має логіку блоку ітератора, і метод відкритих поверхонь, який робить нульову перевірку та повертає ітератор. Тепер, коли GetFrobsвикликається, нульова перевірка відбувається негайно, а потім GetFrobsForRealвиконується, коли послідовність повторюється.

Якщо ви вивчите опорне джерело LINQна «Об’єкти», ви побачите, що ця методика використовується впродовж усього. Це трохи більш незграбно писати, але це значно спрощує налагодження помилок недійсності. Оптимізуйте свій код для зручності абонента, а не зручності автора .

Примітка про нульові відміни в небезпечному коді

C#має "небезпечний" режим, який, як випливає з назви, надзвичайно небезпечний, оскільки нормальні механізми безпеки, що забезпечують безпеку пам’яті та безпеку типу, не застосовуються. Ви не повинні писати небезпечний код, якщо не маєте глибокого та глибокого розуміння того, як працює пам'ять .

У небезпечному режимі вам слід знати два важливі факти:

  • перенаправлення нульового вказівника створює той самий виняток, що і перенаправлення нульової посилання
  • перенаправлення недійсного ненульового покажчика може призвести до цього винятку за деяких обставин

Щоб зрозуміти, чому це, це допомагає зрозуміти, як .NET в першу чергу створює нульові винятки з відновлення. (Ці деталі стосуються .NET, що працює в Windows; інші операційні системи використовують аналогічні механізми.)

Пам'ять віртуалізується в Windows; кожен процес отримує віртуальний простір пам'яті з багатьох "сторінок" пам'яті, які відслідковуються операційною системою. На кожній сторінці пам’яті встановлені прапори, які визначають, яким чином вона може бути використана: читати, писати, виконувати тощо. Низька сторінка позначається як «видають помилку , якщо коли - небудь використовували в будь-якому випадку».

І нульовий вказівник, і нульова посилання C#внутрішньо представлені як нульове число, і тому будь-яка спроба знеструмлення його у відповідному сховищі пам’яті призводить до помилки операційної системи. Потім .NET виконує виявлення цієї помилки та перетворює її на виняток з нульовим відхиленням.

Ось чому перенаправлення як нульового покажчика, так і нульової посилання створює той самий виняток.

А як щодо другого пункту? Перенаправлення будь-якого недійсного вказівника, який потрапляє на найнижчу сторінку віртуальної пам'яті, викликає ту саму помилку операційної системи і, отже, той самий виняток.

Чому це має сенс? Ну, припустимо, у нас є структура, що містить два входи та некерований покажчик, рівний нулю. Якщо ми спробуємо знеструмити другий int в структурі, CLRто не буде спроба отримати доступ до сховища в нульовому місці; він отримає доступ до сховища за адресою чотири. Але логічно це нульове відхилення, оскільки ми дістаємося до цієї адреси через null.

Якщо ви працюєте з небезпечним кодом і отримуєте нульовий виняток відношення, просто пам’ятайте, що покажчик, що порушує порушення, не повинен бути нульовим. Це може бути будь-яке місце на нижній сторінці, і цей виняток буде створено.


55
Можливо, це німий коментар, але хіба перший і кращий спосіб уникнути цієї проблеми - ініціалізація об'єкта? Для мене, якщо ця помилка трапляється, це зазвичай тому, що я забув ініціалізувати щось на зразок елемента масиву. Я думаю, що набагато рідше визначати об'єкт як нульовий, а потім посилатись на нього. Можливо, дайте спосіб вирішити кожну проблему, суміжну з описом. Ще хороший пост.
JPK

30
Що робити, якщо немає об'єкта, а швидше повернене значення методу чи властивості?
Джон Сондерс

6
Приклад книги / автора трохи дивний .... Як це взагалі складати? Як інтелігенція навіть працює? Що це, мені не добре з комп'ютером ...

5
@Will: чи допомагає моє останнє редагування? Якщо ні, то будь ласка, будьте більш чіткими щодо того, що ви бачите як проблему.
Джон Сондерс

6
@JohnSaunders О, ні, вибачте, я мав на увазі версію цього ініціалізатора об'єктів. new Book { Author = { Age = 45 } };Як навіть внутрішня ініціалізація ... Я не можу придумати ситуацію, коли внутрішній ініт коли-небудь спрацював би, але він компілює та інтелігує твори ... Якщо тільки структури?

311

Виняток NullReference - Visual Basic

NullReference ExceptionДля Visual Basic нічим не відрізняється від такого в C # . Зрештою, вони обидва звітують про той самий виняток, визначений у .NET Framework, який вони обидва використовують. Причини, унікальні для Visual Basic, рідкісні (можливо, лише одна).

У цій відповіді будуть використані терміни, синтаксис та контекст Visual Basic. Використовувані приклади походять із великої кількості попередніх запитань щодо переповнення стека. Це для досягнення максимальної актуальності, використовуючи типи ситуацій, які часто спостерігаються у публікаціях. Трохи більше пояснень надається і тим, хто може це потребувати. Приклад, подібний до вашого, дуже ймовірно, перерахований тут.

Примітка:

  1. Це на основі концепції: немає коду, який потрібно вставити у ваш проект. Він покликаний допомогти вам зрозуміти, що викликає NullReferenceException(NRE), як його знайти, як виправити та як уникнути. NRE може бути викликаний багатьма способами, тому це навряд чи буде вашою єдиною зустріччю.
  2. На прикладах (із записів Stack Overflow) не завжди вказується найкращий спосіб зробити щось в першу чергу.
  3. Зазвичай використовується найпростіший засіб.

Основне значення

Повідомлення "Об'єкт не встановлено на екземпляр" Об'єкта " означає, що ви намагаєтесь використовувати об'єкт, який не був ініціалізований. Це зводиться до одного з таких:

  • Ваш код оголосив змінну об'єкта, але він не ініціалізував його (створити примірник або " інстанціювати " його)
  • Щось, на вашу думку, ваш код ініціалізував об’єкт, не робило
  • Можливо, інший код передчасно визнав недійсним об'єкт, який все ще використовується

Пошук причини

Оскільки проблема - це посилання на об'єкт Nothing, відповідь полягає в тому, щоб вивчити їх, щоб з'ясувати, яка саме. Потім визначте, чому вона не ініціалізована. Тримайте курсор миші на різних змінних, і Visual Studio (VS) покаже їх значення - винуватець буде Nothing.

Відображення налагодження IDE

Ви також повинні видалити будь-які блоки Try / Catch із відповідного коду, особливо ті, де у блоці Catch нічого немає. Це призведе до збою вашого коду, коли він намагається використовувати об'єкт, який є Nothing. Це те, що ви хочете, тому що воно визначить точне місце виникнення проблеми та дозволить визначити об'єкт, що його викликає.

А MsgBoxв лові, який відображатиметься, Error while...буде мало допомогти. Цей метод також призводить до дуже поганих запитань щодо переповнення стека, оскільки ви не можете описати фактичний виняток, залучений об'єкт або навіть рядок коду, де це відбувається.

Ви також можете скористатися Locals Window( Налагодження -> Windows -> Місцеві організації ) для вивчення своїх об'єктів.

Після того, як ви дізнаєтеся, що і де проблема, це виправити, як правило, досить легко і швидше, ніж ставити нове запитання.

Дивись також:

Приклади та засоби захисту

Об'єкти класу / Створення екземпляра

Dim reg As CashRegister
...
TextBox1.Text = reg.Amount         ' NRE

Проблема полягає в тому, що Dimне створює CashRegister об'єкта ; він оголошує лише змінну, названу regцього типу. Оголошення змінної об'єкта та створення екземпляра - це дві різні речі.

Засіб усунення

NewОператор часто може бути використаний для створення екземпляра при оголошенні його:

Dim reg As New CashRegister        ' [New] creates instance, invokes the constructor

' Longer, more explicit form:
Dim reg As CashRegister = New CashRegister

Коли доцільно створити екземпляр лише пізніше:

Private reg As CashRegister         ' Declare
  ...
reg = New CashRegister()            ' Create instance

Примітка. Не використовуйте Dimзнову в процедурі, включаючи конструктор ( Sub New):

Private reg As CashRegister
'...

Public Sub New()
   '...
   Dim reg As New CashRegister
End Sub

Це створить локальну змінну, regяка існує лише в цьому контексті (підпункті). regЗмінної з рівнем модуля , Scopeякий ви будете використовувати в будь-якому іншому місці залишається Nothing.

Відсутність Newоператора є причиною №1, яку миNullReference Exceptions бачимо у розглянутих питаннях переповнення стека.

Visual Basic намагається зробити процес очищенням неодноразово, використовуючи New: Використання NewОператора створює новий об’єкт і викликає Sub New- конструктор - там, де ваш об’єкт може виконати будь-яку іншу ініціалізацію.

Щоб було зрозуміло, Dim(або Private) оголошує лише змінну та її Type. Обсяг змінної - чи існує вона для всього модуля / класу або є локальним для процедури - визначається , де вона оголошена. Private | Friend | Publicвизначає рівень доступу, а не Обсяг .

Для отримання додаткової інформації див:


Масиви

Масиви також повинні бути примірниками:

Private arr as String()

Цей масив лише оголошений, а не створений. Існує кілька способів ініціалізації масиву:

Private arr as String() = New String(10){}
' or
Private arr() As String = New String(10){}

' For a local array (in a procedure) and using 'Option Infer':
Dim arr = New String(10) {}

Примітка: Починаючи з VS 2010, коли ініціалізувати локальний масив за допомогою літералу Option Infer, а As <Type>та Newелементи необов'язково:

Dim myDbl As Double() = {1.5, 2, 9.9, 18, 3.14}
Dim myDbl = New Double() {1.5, 2, 9.9, 18, 3.14}
Dim myDbl() = {1.5, 2, 9.9, 18, 3.14}

Тип даних та розмір масиву випливають із даних, які призначаються. Заяви на рівні класу / модуля по- , як і раніше вимагають As <Type>з Option Strict:

Private myDoubles As Double() = {1.5, 2, 9.9, 18, 3.14}

Приклад: масив об’єктів класу

Dim arrFoo(5) As Foo

For i As Integer = 0 To arrFoo.Count - 1
   arrFoo(i).Bar = i * 10       ' Exception
Next

Масив створено, але Fooоб’єктів у ньому немає.

Засіб усунення

For i As Integer = 0 To arrFoo.Count - 1
    arrFoo(i) = New Foo()         ' Create Foo instance
    arrFoo(i).Bar = i * 10
Next

Використання List(Of T)завіту ускладнить наявність елемента без дійсного об'єкта:

Dim FooList As New List(Of Foo)     ' List created, but it is empty
Dim f As Foo                        ' Temporary variable for the loop

For i As Integer = 0 To 5
    f = New Foo()                    ' Foo instance created
    f.Bar =  i * 10
    FooList.Add(f)                   ' Foo object added to list
Next

Для отримання додаткової інформації див:


Списки та колекції

Колекції .NET (яких існує багато різновидів - Списки, Словник тощо) також повинні бути створені або створені.

Private myList As List(Of String)
..
myList.Add("ziggy")           ' NullReference

Ви отримуєте той самий виняток з тієї ж причини - myListбув лише оголошений, але жоден екземпляр не створений. Засіб той же:

myList = New List(Of String)

' Or create an instance when declared:
Private myList As New List(Of String)

Загальний огляд - це клас, який використовує колекцію Type:

Public Class Foo
    Private barList As List(Of Bar)

    Friend Function BarCount As Integer
        Return barList.Count
    End Function

    Friend Sub AddItem(newBar As Bar)
        If barList.Contains(newBar) = False Then
            barList.Add(newBar)
        End If
    End Function

Будь-яка процедура призведе до NRE, оскільки barListвона лише оголошена, а не моментальна. Створення екземпляра Fooне буде також створювати екземпляр внутрішнього barList. Можливо, це було наміром зробити в конструкторі:

Public Sub New         ' Constructor
    ' Stuff to do when a new Foo is created...
    barList = New List(Of Bar)
End Sub

Як і раніше, це неправильно:

Public Sub New()
    ' Creates another barList local to this procedure
     Dim barList As New List(Of Bar)
End Sub

Для отримання додаткової інформації див. List(Of T)Клас .


Об'єкти постачальника даних

Робота з базами даних являє багато можливостей для NullReference , тому що може бути багато об'єктів ( Command, Connection, Transaction, Dataset, DataTable, DataRows....) у використанні відразу. Примітка. Не важливо, якого постачальника даних ви використовуєте - MySQL, SQL Server, OleDB тощо - поняття однакові.

Приклад 1

Dim da As OleDbDataAdapter
Dim ds As DataSet
Dim MaxRows As Integer

con.Open()
Dim sql = "SELECT * FROM tblfoobar_List"
da = New OleDbDataAdapter(sql, con)
da.Fill(ds, "foobar")
con.Close()

MaxRows = ds.Tables("foobar").Rows.Count      ' Error

Як і раніше, dsоб’єкт Dataset був оголошений, але екземпляр так і не був створений. DataAdapterЗаповнить існуючий DataSet, чи не створити. У цьому випадку, оскільки dsце локальна змінна, IDE попереджає, що це може статися:

img

Коли декларується змінною рівня модуля / класу, як це здається con, компілятор не може знати, чи був об'єкт створений процедурою вище. Не ігноруйте попередження.

Засіб усунення

Dim ds As New DataSet

Приклад 2

ds = New DataSet
da = New OleDBDataAdapter(sql, con)
da.Fill(ds, "Employees")

txtID.Text = ds.Tables("Employee").Rows(0).Item(1)
txtID.Name = ds.Tables("Employee").Rows(0).Item(2)

Помилка проблема тут: Employeesпроти Employee. Не було DataTableстворено імені "Співробітник", тому NullReferenceExceptionрезультати намагаються отримати доступ до нього. Іншою потенційною проблемою є припущення, що це Itemsможе бути не так, коли SQL містить пункт WHERE.

Засіб усунення

Оскільки для цього використовується одна таблиця, використання Tables(0)дозволить уникнути орфографічних помилок. Експертиза Rows.Countтакож може допомогти:

If ds.Tables(0).Rows.Count > 0 Then
    txtID.Text = ds.Tables(0).Rows(0).Item(1)
    txtID.Name = ds.Tables(0).Rows(0).Item(2)
End If

Fillце функція, що повертає кількість Rowsпостраждалих, які також можна перевірити:

If da.Fill(ds, "Employees") > 0 Then...

Приклад 3

Dim da As New OleDb.OleDbDataAdapter("SELECT TICKET.TICKET_NO,
        TICKET.CUSTOMER_ID, ... FROM TICKET_RESERVATION AS TICKET INNER JOIN
        FLIGHT_DETAILS AS FLIGHT ... WHERE [TICKET.TICKET_NO]= ...", con)
Dim ds As New DataSet
da.Fill(ds)

If ds.Tables("TICKET_RESERVATION").Rows.Count > 0 Then

Подання DataAdapterбуде надано, TableNamesяк показано в попередньому прикладі, але воно не розбирає імена з таблиці SQL або таблиці баз даних. В результаті ds.Tables("TICKET_RESERVATION")посилається на неіснуючу таблицю.

Усунення такої ж, посилання на таблицю з допомогою індексу:

If ds.Tables(0).Rows.Count > 0 Then

Дивіться також Клас DataTable .


Шлях до об'єкта / Вкладений

If myFoo.Bar.Items IsNot Nothing Then
   ...

Код тестується лише в Itemsтой час, як обидва, myFooа Barтакож може бути Ніщо. Засіб , щоб перевірити весь ланцюжок або шлях об'єктів по одному за раз:

If (myFoo IsNot Nothing) AndAlso
    (myFoo.Bar IsNot Nothing) AndAlso
    (myFoo.Bar.Items IsNot Nothing) Then
    ....

AndAlsoважливо. Подальші тести не виконуватимуться, коли Falseвиникне перша умова. Це дозволяє коду безпечно "свердлити" в об'єкт (и) один "рівень" за один раз, оцінюючи myFoo.Barлише після того, як (і якщо) myFooбуде визнано дійсним. Об'єктні ланцюги або шляхи можуть отримати досить довгий час кодування складних об'єктів:

myBase.myNodes(3).Layer.SubLayer.Foo.Files.Add("somefilename")

Неможливо посилатися на що-небудь "низхідне" nullоб'єкта. Це стосується також елементів управління:

myWebBrowser.Document.GetElementById("formfld1").InnerText = "some value"

Тут myWebBrowserабо Documentможе бути Ніщо, або formfld1елемент може не існувати.


UI управління

Dim cmd5 As New SqlCommand("select Cartons, Pieces, Foobar " _
     & "FROM Invoice where invoice_no = '" & _
     Me.ComboBox5.SelectedItem.ToString.Trim & "' And category = '" & _
     Me.ListBox1.SelectedItem.ToString.Trim & "' And item_name = '" & _
     Me.ComboBox2.SelectedValue.ToString.Trim & "' And expiry_date = '" & _
     Me.expiry.Text & "'", con)

Крім усього іншого, цей код не передбачає, що користувач, можливо, не вибрав щось в одному або декількох елементах управління інтерфейсом. ListBox1.SelectedItemцілком може бути Nothing, тому ListBox1.SelectedItem.ToStringце призведе до NRE.

Засіб усунення

Перевірте дані перед його використанням (також використовуйте Option Strictі параметри SQL):

Dim expiry As DateTime         ' for text date validation
If (ComboBox5.SelectedItems.Count > 0) AndAlso
    (ListBox1.SelectedItems.Count > 0) AndAlso
    (ComboBox2.SelectedItems.Count > 0) AndAlso
    (DateTime.TryParse(expiry.Text, expiry) Then

    '... do stuff
Else
    MessageBox.Show(...error message...)
End If

Як варіант, ви можете використовувати (ComboBox5.SelectedItem IsNot Nothing) AndAlso...


Основні візуальні форми

Public Class Form1

    Private NameBoxes = New TextBox(5) {Controls("TextBox1"), _
                   Controls("TextBox2"), Controls("TextBox3"), _
                   Controls("TextBox4"), Controls("TextBox5"), _
                   Controls("TextBox6")}

    ' same thing in a different format:
    Private boxList As New List(Of TextBox) From {TextBox1, TextBox2, TextBox3 ...}

    ' Immediate NRE:
    Private somevar As String = Me.Controls("TextBox1").Text

Це досить поширений спосіб отримати НРЕ. У C #, залежно від того, як це закодовано, IDE повідомляє, що Controlsне існує в поточному контексті, або "не може посилатися на нестатичний член". Отже, певною мірою це лише ситуація з ВБ. Він також складний, оскільки може призвести до каскаду відмов.

Масиви та колекції не можна ініціалізувати таким чином. Цей код ініціалізації запуститься до того, як конструктор створить Formабо Controls. В результаті:

  • Списки та колекція просто будуть порожніми
  • Масив буде містити п'ять елементів нічого
  • somevarПризначення призведе до негайного Ярду , тому що нічого не має .Textвластивості

Пізніше елементи масиву посилання призведуть до появи NRE. Якщо ви це зробите Form_Loadвнаслідок непарної помилки, IDE не може повідомити про виняток, коли це відбувається. Виняток з’явиться пізніше, коли ваш код намагатиметься використовувати масив. Цей "мовчазний виняток" детально описаний у цій публікації . Для наших цілей ключовим є те, що коли трапляється щось катастрофічне під час створення форми ( Sub Newабо Form Loadподії), винятки можуть залишатися не повідомленими, код виходить з процедури і просто відображає форму.

Оскільки жоден інший код у вашому Sub Newабо Form Loadподії не працює після NRE, дуже багато інших речей можна залишити неініціалізованими.

Sub Form_Load(..._
   '...
   Dim name As String = NameBoxes(2).Text        ' NRE
   ' ...
   ' More code (which will likely not be executed)
   ' ...
End Sub

Зверніть увагу, це стосується будь-яких посилань на контроль та компоненти, що роблять це незаконним там, де вони є:

Public Class Form1

    Private myFiles() As String = Me.OpenFileDialog1.FileName & ...
    Private dbcon As String = OpenFileDialog1.FileName & ";Jet Oledb..."
    Private studentName As String = TextBox13.Text

Частковий засіб

Цікаво , що VB не дає попередження, а засіб для оголосити контейнери на рівні форми, але форматувати їх в обробник події завантаження форми , коли елементи управління роблять існує. Це можна зробити Sub New, якщо ваш код після InitializeComponentдзвінка:

' Module level declaration
Private NameBoxes as TextBox()
Private studentName As String

' Form Load, Form Shown or Sub New:
'
' Using the OP's approach (illegal using OPTION STRICT)
NameBoxes = New TextBox() {Me.Controls("TextBox1"), Me.Controls("TestBox2"), ...)
studentName = TextBox32.Text           ' For simple control references

Код масиву ще може бути поза лісом. Будь-які елементи керування, які знаходяться в керуванні контейнером (як-от GroupBoxабо Panel), не знайдуться в Me.Controls; вони будуть знаходитись у колекції Controls на цій панелі або GroupBox. Також контроль не буде повернутий, коли ім'я керування неправильно написано ( "TeStBox2"). У таких випадках Nothingзнову зберігатимуться в цих елементах масиву, і при спробі посилання на нього з'явиться NRE.

Тепер їх слід легко знайти, коли ви знаєте, що шукаєте: VS показує вам помилку ваших шляхів

"Button2" знаходиться на a Panel

Засіб усунення

Замість побічних посилань по імені, використовуючи Controlsколекцію форми , використовуйте контрольну посилання:

' Declaration
Private NameBoxes As TextBox()

' Initialization -  simple and easy to read, hard to botch:
NameBoxes = New TextBox() {TextBox1, TextBox2, ...)

' Initialize a List
NamesList = New List(Of TextBox)({TextBox1, TextBox2, TextBox3...})
' or
NamesList = New List(Of TextBox)
NamesList.AddRange({TextBox1, TextBox2, TextBox3...})

Функція Повернення Нічого

Private bars As New List(Of Bars)        ' Declared and created

Public Function BarList() As List(Of Bars)
    bars.Clear
    If someCondition Then
        For n As Integer = 0 to someValue
            bars.Add(GetBar(n))
        Next n
    Else
        Exit Function
    End If

    Return bars
End Function

Це випадок, коли IDE попередить вас, що " не всі шляхи повертають значення і NullReferenceExceptionможе призвести до результату ". Ви можете придушити попередження, замінивши Exit Functionз Return Nothing, але це не вирішує проблему. Все, що намагається використовувати віддачу, коли someCondition = Falseпризведе до NRE:

bList = myFoo.BarList()
For Each b As Bar in bList      ' EXCEPTION
      ...

Засіб усунення

Замініть Exit Functionу функції на Return bList. Повернення порожнього List - це не те саме, що повернення Nothing. Якщо є ймовірність, що повернутий об’єкт може бути Nothing, протестуйте перед його використанням:

 bList = myFoo.BarList()
 If bList IsNot Nothing Then...

Погано реалізований спробу / ловити

Неправильно реалізований Try / Catch може приховати проблему та призвести до нових:

Dim dr As SqlDataReader
Try
    Dim lnk As LinkButton = TryCast(sender, LinkButton)
    Dim gr As GridViewRow = DirectCast(lnk.NamingContainer, GridViewRow)
    Dim eid As String = GridView1.DataKeys(gr.RowIndex).Value.ToString()
    ViewState("username") = eid
    sqlQry = "select FirstName, Surname, DepartmentName, ExtensionName, jobTitle,
             Pager, mailaddress, from employees1 where username='" & eid & "'"
    If connection.State <> ConnectionState.Open Then
        connection.Open()
    End If
    command = New SqlCommand(sqlQry, connection)

    'More code fooing and barring

    dr = command.ExecuteReader()
    If dr.Read() Then
        lblFirstName.Text = Convert.ToString(dr("FirstName"))
        ...
    End If
    mpe.Show()
Catch

Finally
    command.Dispose()
    dr.Close()             ' <-- NRE
    connection.Close()
End Try

Це випадок, коли об’єкт не створюється так, як очікувалося, але також демонструє корисну корисність порожнього Catch.

У SQL є додаткова кома (після 'mailaddress'), яка призводить до виключення в .ExecuteReader. Після того, Catchяк нічого не робить, Finallyнамагається виконати очищення, але оскільки ви не можете Closeотримати DataReaderоб'єкт, це абсолютно нові NullReferenceExceptionрезультати.

Порожній Catchблок - дитячий майданчик. Цей ОП був здивований, чому він отримує НРЕ в Finallyблоці. В інших ситуаціях порожній Catchможе призвести до чогось ще набагато далі за течією, що перебуває вниз за течією, і призведе до того, що ви витратите час на пошук неправильних речей у неправильному місці для проблеми. (Вищеописаний "мовчазний виняток" надає однакове розважальне значення.)

Засіб усунення

Не використовуйте порожні блоки "Try / Catch" - нехай код виходить з ладу, щоб ви могли а) визначити причину b) визначити місце розташування та в) застосувати належний засіб захисту. Блоки "Try / Catch" не приховують винятків від людини, яка має унікальну кваліфікацію для їх виправлення - розробника.


DBNull - не те саме, що нічого

For Each row As DataGridViewRow In dgvPlanning.Rows
    If Not IsDBNull(row.Cells(0).Value) Then
        ...

IsDBNullФункція використовується для перевірки , якщо значення одно System.DBNull: З MSDN:

Значення System.DBNull вказує на те, що Об'єкт представляє відсутні або неіснуючі дані. DBNull не є тим самим, що нічого, що вказує на те, що змінна ще не була ініціалізована.

Засіб усунення

If row.Cells(0) IsNot Nothing Then ...

Як і раніше, ви можете протестувати на Ніщо, а потім на конкретне значення:

If (row.Cells(0) IsNot Nothing) AndAlso (IsDBNull(row.Cells(0).Value) = False) Then

Приклад 2

Dim getFoo = (From f In dbContext.FooBars
               Where f.something = something
               Select f).FirstOrDefault

If Not IsDBNull(getFoo) Then
    If IsDBNull(getFoo.user_id) Then
        txtFirst.Text = getFoo.first_name
    Else
       ...

FirstOrDefaultповертає перший елемент або значення за замовчуванням, яке Nothingстосується типів посилань і ніколи DBNull:

If getFoo IsNot Nothing Then...

Управління

Dim chk As CheckBox

chk = CType(Me.Controls(chkName), CheckBox)
If chk.Checked Then
    Return chk
End If

Якщо a CheckBoxз chkNameне можна знайти (або існує в a GroupBox), то chkбуде "Nothing" і спроба посилання на будь-яке властивість призведе до виключення.

Засіб усунення

If (chk IsNot Nothing) AndAlso (chk.Checked) Then ...

DataGridView

Періодично у DGV спостерігається кілька химерностей:

dgvBooks.DataSource = loan.Books
dgvBooks.Columns("ISBN").Visible = True       ' NullReferenceException
dgvBooks.Columns("Title").DefaultCellStyle.Format = "C"
dgvBooks.Columns("Author").DefaultCellStyle.Format = "C"
dgvBooks.Columns("Price").DefaultCellStyle.Format = "C"

Якщо dgvBooksє AutoGenerateColumns = True, він буде створювати стовпці, але він не називає їх, тому вищевказаний код виходить з ладу, коли він посилається на них по імені.

Засіб усунення

Назвіть стовпці вручну або посилайтеся на індекс:

dgvBooks.Columns(0).Visible = True

Приклад 2 - Остерігайтеся NewRow

xlWorkSheet = xlWorkBook.Sheets("sheet1")

For i = 0 To myDGV.RowCount - 1
    For j = 0 To myDGV.ColumnCount - 1
        For k As Integer = 1 To myDGV.Columns.Count
            xlWorkSheet.Cells(1, k) = myDGV.Columns(k - 1).HeaderText
            xlWorkSheet.Cells(i + 2, j + 1) = myDGV(j, i).Value.ToString()
        Next
    Next
Next

Коли ваш DataGridViewє , AllowUserToAddRowsяк True(за замовчуванням), Cellsв заготівлі / новий рядок в нижній частині все буде містити Nothing. Більшість спроб використання вмісту (наприклад, ToString) призведе до появи NRE.

Засіб усунення

Використовуйте For/Eachцикл і протестуйте IsNewRowвластивість, щоб визначити, чи є він останнім рядком. Це працює AllowUserToAddRows, правда чи ні:

For Each r As DataGridViewRow in myDGV.Rows
    If r.IsNewRow = False Then
         ' ok to use this row

Якщо ви використовуєте For nцикл, змініть кількість рядків або використовуйте, Exit Forколи IsNewRowце правда.


Мої настройки (StringCollection)

За певних обставин спроба використовувати предмет, з My.Settingsякого є, StringCollectionможе спричинити NullReference під час першого використання. Рішення таке ж, але не таке очевидне. Поміркуйте:

My.Settings.FooBars.Add("ziggy")         ' foobars is a string collection

Оскільки VB керує налаштуваннями для вас, розумно очікувати, що колекція буде ініціалізована. Це буде, але лише якщо ви раніше додали початковий запис до колекції (у редакторі налаштувань). Оскільки колекція (мабуть) ініціалізується, коли елемент додано, він залишається, Nothingколи в редакторі налаштувань немає елементів, які потрібно додати.

Засіб усунення

Ініціалізуйте колекцію налаштувань у Loadобробці подій форми, якщо / коли потрібно:

If My.Settings.FooBars Is Nothing Then
    My.Settings.FooBars = New System.Collections.Specialized.StringCollection
End If

Зазвичай Settingsколекцію потрібно буде ініціалізувати лише при першому запуску програми. Альтернативним засобом захисту є додавання початкової вартості до колекції в Project -> Settings | FooBars , збережіть проект, а потім видаліть підроблене значення.


Ключові моменти

Ви, напевно, забули Newоператора.

або

Щось, що ви припускали, буде виконувати бездоганно, щоб повернути ініціалізований об'єкт у ваш код, ні.

Не ігноруйте попередження компілятора (ніколи) та використовуйте Option Strict On(завжди).


Виняток MSDN NullReference


226

Інший сценарій - це коли ви передаєте нульовий об'єкт у тип значення . Наприклад, код нижче:

object o = null;
DateTime d = (DateTime)o;

Це кине NullReferenceExceptionна акторський склад. У наведеному вище зразку це здається цілком очевидним, але це може статися в більш «запізнілих» складних сценаріях, коли нульовий об’єкт повернуто з якогось коду, яким ви не володієте, а кастинг, наприклад, генерується деякою автоматичною системою.

Одним із прикладів цього є цей простий фрагмент прив’язки ASP.NET з керуванням календаря:

<asp:Calendar runat="server" SelectedDate="<%#Bind("Something")%>" />

Тут SelectedDateнасправді є властивість - DateTimeтипу - типу CalendarWeb Control, і прив'язка може ідеально повернути щось недійсне. Неявний генератор ASP.NET створить фрагмент коду, який буде еквівалентний наведеному вище коду. І це призведе до того, NullReferenceExceptionщо досить важко помітити, оскільки він лежить у створеному ASP.NET коді, який добре поєднує ...


7
Чудовий улов. Однолінійний спосіб уникнути:DateTime x = (DateTime) o as DateTime? ?? defaultValue;
Серж Шульц

159

Це означає, що змінна, про яку йдеться, не вказується ні на що. Я міг би генерувати це так:

SqlConnection connection = null;
connection.Open();

Це призведе до помилки, оскільки, хоча я оголосив змінну " connection", вона нічого не вказує. Коли я намагаюся зателефонувати учаснику " Open", його немає для вирішення, і це призведе до помилки.

Щоб уникнути цієї помилки:

  1. Завжди ініціалізуйте свої об'єкти, перш ніж ви намагаєтесь щось зробити з ними.
  2. Якщо ви не впевнені, чи об’єкт недійсний, перевірте його object == null.

Інструмент Resharper JetBrains визначить кожне місце у вашому коді, яке має можливість помилки з нульовим посиланням, що дозволяє ввести нульову перевірку. Ця помилка є джерелом помилок номер один, IMHO.


3
Інструмент Resharper JetBrains визначить кожне місце у вашому коді, яке має можливість помилки з нульовим посиланням. Це неправильно. У мене є рішення без цього виявлення, але код час від часу призводить до виключення. Я підозрюю, що іноді їх не можна виявити - принаймні ними - коли задіяна багатопотокова редакція, але я не можу далі коментувати, оскільки я ще не визначив місцезнаходження моєї помилки.
j riv

Але як це вирішити, коли NullReferenceException приходить у проект HttpContext.Current.Responce.Clear (). Це не вирішується жодним із вищезазначених рішень. тому що, створюючи свій об'єкт HttpContext, тоді виникає помилка "Розв’язання перевантаження не вдалося, оскільки жоден доступний" Новий "не приймає цю кількість аргументів.
Sunny Sandeep

157

Це означає, що ваш код використовував змінну посилання на об'єкт, на яку було встановлено нуль (тобто він не посилався на фактичний примірник об'єкта).

Щоб запобігти помилці, об’єкти, які можуть бути нульовими, повинні бути перевірені на нуль перед їх використанням.

if (myvar != null)
{
    // Go ahead and use myvar
    myvar.property = ...
}
else
{
    // Whoops! myvar is null and cannot be used without first
    // assigning it to an instance reference
    // Attempting to use myvar here will result in NullReferenceException
}

96

Майте на увазі, що незалежно від сценарію причина завжди однакова в .NET:

Ви намагаєтеся використовувати контрольну змінну, значення якої Nothing/ null. Коли значення є Nothing/ nullдля посилальної змінної, це означає, що воно насправді не містить посилання на екземпляр будь-якого об'єкта, який існує в купі.

Ви ніколи не привласнювали щось змінній, ніколи не створювали примірник значення, присвоєного змінній, або встановлювали змінну рівним Nothing/ nullвручну, або ви називали функцію, яка встановлювала цю змінну на Nothing/ nullдля вас.


87

Приклад викиду цього винятку: Коли ви намагаєтеся перевірити щось, це недійсне значення.

Наприклад:

string testString = null; //Because it doesn't have a value (i.e. it's null; "Length" cannot do what it needs to do)

if (testString.Length == 0) // Throws a nullreferenceexception
{
    //Do something
} 

Виконання .NET передасть NullReferenceException, коли ви намагаєтесь виконати дію на щось, що не було інстанційованим, тобто кодом, наведеним вище.

У порівнянні з ArgumentNullException, який зазвичай кидається як захисний захід, якщо метод очікує, що те, що передається йому, не є нульовим.

Більш детальна інформація знаходиться в C # NullReferenceException та Null Parameter .


87

Оновлення C # 8.0, 2019: Мінусні типи посилань

C # 8.0 вводить нульові еталонні типи та нерегульовані типи посилань . Тож слід перевіряти лише нульові типи посилань, щоб уникнути NullReferenceException .


Якщо ви не ініціалізували тип посилання і хочете встановити чи прочитати одну з його властивостей, він видасть NullReferenceException .

Приклад:

Person p = null;
p.Name = "Harry"; // NullReferenceException occurs here.

Ви можете просто уникнути цього, перевіривши, чи змінна не є нульовою:

Person p = null;
if (p!=null)
{
    p.Name = "Harry"; // Not going to run to this point
}

Щоб повністю зрозуміти, чому викидається NullReferenceException, важливо знати різницю між типами значень та [ типовими типами] [3].

Отже, якщо ви маєте справу з типами значень , NullReferenceExceptions не може виникнути. Хоча вам потрібно бути настороженими при роботі з еталонними типами !

Тільки типи посилань, як випливає з назви, можуть містити посилання або вказувати буквально на ніщо (або "null"). Тоді як типи значень завжди містять значення.

Довідкові типи (їх потрібно перевірити):

  • динамічний
  • об’єкт
  • рядок

Типи значень (їх можна просто проігнорувати):

  • Числові типи
  • Інтегральні типи
  • Типи з плаваючою комою
  • десятковий
  • бул
  • Структури, визначені користувачем

6
-1: оскільки питання "Що таке NullReferenceException", типи значень не мають значення.
Джон Сондерс

21
@John Saunders: Я не згоден. Як розробник програмного забезпечення дійсно важливо вміти розрізняти цінні та референтні типи. інакше люди перевірять, чи є цілі числа нульовими.
Фабіан Біглер

5
Щоправда, просто не в контексті цього питання.
Джон Сондерс

4
Дякую за підказку. Я трохи її покращив і додав приклад вгорі. Я все ще думаю, що згадування типів довідки та цінності є корисним.
Фабіан Біглер

5
Я думаю, ви не додали нічого, що не було в інших відповідях, оскільки це питання передбачає тип посилання.
Джон Сондерс

78

Інший випадок, коли це NullReferenceExceptionsможе статися, - це (неправильне) використання asоператора :

class Book {
    public string Name { get; set; }
}
class Car { }

Car mycar = new Car();
Book mybook = mycar as Book;   // Incompatible conversion --> mybook = null

Console.WriteLine(mybook.Name);   // NullReferenceException

Тут Bookі Carє несумісні типи; a Carнеможливо перетворити / передати в а Book. Коли цей випадок не вдається, asповертається null. Використання mybookпісля цього викликає a NullReferenceException.

Загалом, ви повинні використовувати акторський склад або asнаступне:

Якщо ви очікуєте, що перетворення типів завжди буде успішним (тобто ви знаєте, який об'єкт повинен бути випередженим), тоді вам слід скористатися роллю:

ComicBook cb = (ComicBook)specificBook;

Якщо ви не впевнені в цьому типі, але хочете спробувати використовувати його як певний тип, тоді використовуйте as:

ComicBook cb = specificBook as ComicBook;
if (cb != null) {
   // ...
}

2
Це може статися багато при розпакуванні змінної. Я вважаю, що це трапляється часто в обробниках подій після того, як я змінив тип елемента інтерфейсу, але забув оновити код-позаду.
Брендан

65

Ви використовуєте об'єкт, що містить посилання нульового значення. Тож це є нульовим винятком. У прикладі значення рядка є нульовим, і при перевірці його довжини сталося виключення.

Приклад:

string value = null;
if (value.Length == 0) // <-- Causes exception
{
    Console.WriteLine(value); // <-- Never reached
}

Помилка винятку:

Неопрацьоване виняток:

System.NullReferenceException: Посилання на об'єкт не встановлено для екземпляра об'єкта. в Program.Main ()


1
Як глибоко! Я ніколи не вважав постійною «нульовою» постійною значення. Отже, так C # абстрагує "NullPointer", так? B / c, наскільки я пам'ятаю в C ++, NPE може бути викликаний перенаправленням неініціалізованого покажчика (тобто типу ref в c #), значенням за замовчуванням трапляється адреса, яка не призначена для цього процесу (у багатьох випадках це буде 0, особливо в пізніших версіях C ++, які зробили автоматичну ініціалізацію, яка належить до ОС - f з нею і померти бутом (або просто зловити сигк, на який ОС атакує ваш процес).
Саміс

64

Хоча те, що викликає NullReferenceExceptions та підходи до уникнення / виправлення такого винятку, розглянуто в інших відповідях, те, що багато програмістів ще не дізналися, - як самостійно налагоджувати такі винятки під час розробки.

У Visual Studio це зазвичай легко завдяки відладчику Visual Studio .


По-перше, переконайтеся, що правильна помилка буде зафіксована - див. Як дозволити перелом на 'System.NullReferenceException' у VS2010? Примітка 1

Потім або Почніть з налагодження (F5), або додайте [налагоджувач VS] до запущеного процесу . При нагоді це може бути корисно використовувати Debugger.Break, що підкаже запуск налагоджувача.

Тепер, коли NullReferenceException буде кинуто (або не оброблено), налагоджувач зупиниться (пам'ятаєте правило, встановлене вище?) На рядку, в якому сталося виключення. Іноді помилку буде легко помітити.

Наприклад, у наступному рядку єдиний код, який може спричинити виняток, - якщо його myStringоцінюють на нуль. Це можна перевірити, подивившись у вікно годинника або виконуючи вирази у негайному вікні .

var x = myString.Trim();

У більш розвинених випадках, таких як наступний, вам потрібно скористатися однією з прийомів, описаних вище (Watch або Immediate Windows), щоб перевірити вирази, щоб визначити, чи str1було null або str2null.

var x = str1.Trim() + str2.Trim();

Після того, коли виняток становить кидок був розташований, зазвичай тривіальної причини задом наперед , щоб з'ясувати , де нульове значення було [неправильно] ввів -

Знайдіть час, необхідний для розуміння причини винятку. Перевірте наявність нульових виразів. Перевірте попередні вирази, які могли призвести до таких нульових виразів. Додайте точки перерви та пройдіть через програму, якщо потрібно. Використовуйте налагоджувач.


1 Якщо Break on Throw занадто агресивний і відладчик зупиняється на NPE в .NET або сторонній бібліотеці, Break on User-Unhandled може бути використаний для обмеження вилучених винятків. Крім того, VS2012 представляє Just My Code, який я рекомендую також включити.

Якщо ви налагоджуєте функцію Just My Code увімкнено, поведінка дещо відрізняється. Якщо увімкнено функцію Just My Code, налагоджувач ігнорує винятки загальної мови (CLR) першої можливості, які викидаються за межі Мого коду та не проходять через Мій код


59

Саймон Мур’є подав такий приклад :

object o = null;
DateTime d = (DateTime)o;  // NullReferenceException

де конверсія (викид) з розблокування ( з одного класу або , або з типу інтерфейсу) у тип значення (відмінний від ) сам по собі дає значення . objectSystem.ValueTypeSystem.EnumNullable<>NullReferenceException

В іншому напрямку, A бокс перетворення зNullable<> який має HasValueрівний false до посилальному типу, може дати nullпосилання , яка потім може згодом призвести до NullReferenceException. Класичний приклад:

DateTime? d = null;
var s = d.ToString();  // OK, no exception (no boxing), returns ""
var t = d.GetType();   // Bang! d is boxed, NullReferenceException

Іноді бокс буває по-іншому. Наприклад, із цим негенеричним методом розширення:

public static void MyExtension(this object x)
{
  x.ToString();
}

наступний код буде проблематичним:

DateTime? d = null;
d.MyExtension();  // Leads to boxing, NullReferenceException occurs inside the body of the called method, not here.

Ці випадки виникають через особливі правила, які виконуються під час виконання боксу Nullable<>.


42

Додавання випадку, коли ім'я класу для сутності, що використовується в рамках сутності, є іменем класу для файлу коду веб-форми.

Припустимо, у вас є веб-форма Contact.aspx, кодом якої є клас Контакт, і ви маєте ім'я особи Contact.

Потім наступний код видасть NullReferenceException при виклику контексту.SaveChanges ()

Contact contact = new Contact { Name = "Abhinav"};
var context = new DataContext();
context.Contacts.Add(contact);
context.SaveChanges(); // NullReferenceException at this line

Для повноти класу DataContext

public class DataContext : DbContext 
{
    public DbSet<Contact> Contacts {get; set;}
}

та Контактний клас сутності. Іноді класи сутностей є частковими класами, так що ви можете поширити їх і в інші файли.

public partial class Contact 
{
    public string Name {get; set;}
}

Помилка виникає, коли і сутність, і клас, що знаходиться за кодом, знаходяться в одному просторі імен. Щоб виправити це, перейменуйте клас сутності або клас код, що знаходиться за кодом Contact.aspx.

Причина Я досі не впевнений у своїй причині. Але кожного разу, коли будь-який із класів сутності поширить System.Web.UI.Page, ця помилка виникає.

Для обговорення подивіться на NullReferenceException в DbContext.saveChanges ()


41

Інший загальний випадок, коли можна отримати цей виняток, стосується знущальних класів під час тестування одиниць. Незалежно від використовуваної рамки глузування, ви повинні переконатися, що всі відповідні рівні ієрархії класів належним чином висміюються. Зокрема, з усіх властивостей, на HttpContextякі посилається тестовий код, слід висміяти.

Див. " NullReferenceException, кинутий під час тестування користувальницького AuthorizationAttribute ", для дещо детального прикладу.


40

У мене є інша перспектива відповісти на це. Цей тип відповідей "що ще я можу зробити, щоб цього уникнути? "

Під час роботи на різних рівнях , наприклад у програмі MVC, контролеру потрібні послуги для виклику бізнес-операцій. У таких сценаріях контейнер ін'єкції залежності може використовуватися для ініціалізації служб, щоб уникнути NullReferenceException . Отже, це означає, що вам не потрібно турбуватися про перевірку наявності null, а просто зателефонуйте до служб від контролера, як ніби вони завжди будуть доступні (і ініціалізовані) як синглтон, або прототип.

public class MyController
{
    private ServiceA serviceA;
    private ServiceB serviceB;

    public MyController(ServiceA serviceA, ServiceB serviceB)
    {
        this.serviceA = serviceA;
        this.serviceB = serviceB;
    }

    public void MyMethod()
    {
        // We don't need to check null because the dependency injection container 
        // injects it, provided you took care of bootstrapping it.
        var someObject = serviceA.DoThis();
    }
}

6
-1: це обробляє лише єдиний сценарій - сценарій неініціалізованих залежностей. Це сценарій меншості для NullReferenceException. У більшості випадків це просте непорозуміння того, як працюють об’єкти. Наступні найбільш часті - інші ситуації, коли розробник припускав, що об'єкт буде ініціалізований автоматично.
Джон Сондерс

Зазвичай залежність від ін'єкції не використовується, щоб уникнути NullReferenceException. Я не вірю, що ви тут знайшли загальний сценарій. У будь-якому випадку, якщо ви відредагуєте свою відповідь, щоб вона більше відповідала стилю stackoverflow.com/a/15232518/76337 , тоді я видалю зворотну заяву.
Джон Сондерс

38

На питання "що мені з цим робити" , відповідей може бути багато.

Більш "формальний" спосіб запобігання подібним умовам помилок при розробці - це застосування дизайну за контрактом у вашому коді. Це означає, що вам потрібно встановлювати інваріанти класу та / або навіть функціональні / методи передумови та постумови у вашій системі під час розробки.

Одним словом, інваріанти класів гарантують, що у вашому класі будуть деякі обмеження, які не будуть порушені при звичайному використанні (і, отже, клас не буде потрапить у непослідовний стан). Передумови означають, що дані, введені як вхід до функції / методу, повинні відповідати деяким наборам обмежень і ніколи не порушувати їх, а пост-умови означають, що вихід / функція / метод повинні знову слідувати встановленим обмеженням, не порушуючи їх ніколи. Умови контракту ніколи не повинні порушуватися під час виконання програми без помилок, тому дизайн за контрактом перевіряється на практиці в режимі налагодження, при цьому він вимикається у випусках , щоб забезпечити максимальну ефективність розробленої системи.

Таким чином, ви можете уникнути NullReferenceExceptionвипадків, які є результатом порушення встановлених обмежень. Наприклад, якщо ви використовуєте властивість об'єкта Xв класі та пізніше намагаєтеся викликати один із його методів і Xмає нульове значення, то це призведе до NullReferenceException:

public X { get; set; }

public void InvokeX()
{
    X.DoSomething(); // if X value is null, you will get a NullReferenceException
}

Але якщо встановити "властивість X ніколи не повинно мати нульове значення" як попередню умову методу, ви можете запобігти описаному раніше сценарію:

//Using code contracts:
[ContractInvariantMethod]
protected void ObjectInvariant () 
{
    Contract.Invariant ( X != null );
    //...
}

З цієї причини існує проект Code Contracts для програм .NET.

Як варіант, дизайн за контрактом можна застосувати, використовуючи твердження .

ОНОВЛЕННЯ: Варто згадати, що цей термін був введений Бертран Меєром у зв'язку з його розробкою мови програмування Ейфеля .


2
Я думав додати це, оскільки про це ніхто не згадував, і наскільки це існує як підхід, мій намір полягав у збагаченні теми.
Нік Лулудакіс

2
Дякую за збагачення теми. Я висловив свою думку щодо вашого доповнення. Тепер інші можуть робити те саме.
Джон Сондерс

2
Я вважав, що це є вагомим доповненням до теми, враховуючи, що це надзвичайно переглянута тема. Я чув про кодові контракти і раніше, і це було гарним нагадуванням про їх використання.
VoteCoffee

36

A NullReferenceExceptionкидається, коли ми намагаємося отримати доступ до властивостей нульового об’єкта або коли значення рядка стає порожнім, і ми намагаємося отримати доступ до рядкових методів.

Наприклад:

  1. Коли доступ до рядкового методу порожнього рядка:

    string str = string.Empty;
    str.ToLower(); // throw null reference exception
  2. Коли доступ до властивості нульового об’єкта:

    Public Class Person {
        public string Name { get; set; }
    }
    Person objPerson;
    objPerson.Name  /// throw Null refernce Exception 

2
Це неправильно. String.Empty.ToLower()не викине нульовий посилання на виняток. Він являє собою фактичний рядок, хоч і порожній (тобто ""). Оскільки в цьому об’єкті слід закликати ToLower(), не було б сенсу викидати туди нульовий виняток.
Кьяртан

31

TL; DR: Спробуйте використовувати Html.PartialзамістьRenderpage


Я отримував, Object reference not set to an instance of an objectколи намагався надати вигляд у межах подання, надіславши йому модель, наприклад:

@{
    MyEntity M = new MyEntity();
}
@RenderPage("_MyOtherView.cshtml", M); // error in _MyOtherView, the Model was Null

Налагодження показало, що модель була Null всередині MyOtherView. Поки я не змінив його на:

@{
    MyEntity M = new MyEntity();
}
@Html.Partial("_MyOtherView.cshtml", M);

І це спрацювало.

Крім того, причиною, з якої мені не довелося Html.Partialпочинати, було те, що Visual Studio іноді кидає під виглядом помилки кричущі лінії, Html.Partialякщо вона знаходиться в іншому побудованому foreachциклі, хоча це насправді не помилка:

@inherits System.Web.Mvc.WebViewPage
@{
    ViewBag.Title = "Entity Index";
    List<MyEntity> MyEntities = new List<MyEntity>();
    MyEntities.Add(new MyEntity());
    MyEntities.Add(new MyEntity());
    MyEntities.Add(new MyEntity());
}
<div>
    @{
        foreach(var M in MyEntities)
        {
            // Squiggly lines below. Hovering says: cannot convert method group 'partial' to non-delegate type Object, did you intend to envoke the Method?
            @Html.Partial("MyOtherView.cshtml");
        }
    }
</div>

Але мені вдалося запустити додаток без проблем із цією "помилкою". Я зміг позбутися помилки, змінивши структуру foreachциклу, щоб виглядати так:

@foreach(var M in MyEntities){
    ...
}

Хоча я відчуваю, що це було тому, що Visual Studio неправильно читав амперсанди та дужки.


Ви хотіли Html.Partial, ні@Html.Partial
Джон Сондерс

Також покажіть, будь ласка, який рядок викинув виняток, і чому.
Джон Сондерс

Помилка сталася в MyOtherView.cshtml, яку я сюди не включив, оскільки Модель не надсилалась належним чином (це було Null), тому я знав, що помилка була в тому, як я надсилаю Модель.
Травіс Хетер

22

Що ви можете з цим зробити?

Тут є багато хороших відповідей, що пояснюють, що таке нульова посилання та як її налагодити. Але про те, як запобігти проблемі або принаймні полегшити вилов, є дуже мало.

Перевірте аргументи

Наприклад, методи можуть перевірити різні аргументи, щоб побачити, чи є вони нульовими, і кинути an ArgumentNullException, виняток, очевидно, створений саме для цієї мети.

Конструктор для ArgumentNullExceptionрівних приймає назву параметра та повідомлення як аргументи, щоб ви могли точно сказати розробнику, у чому проблема.

public void DoSomething(MyObject obj) {
    if(obj == null) 
    {
        throw new ArgumentNullException("obj", "Need a reference to obj.");
    }
}

Використовуйте Інструменти

Також є кілька бібліотек, які можуть допомогти. Наприклад, "Resharper" може надати вам попередження під час написання коду, особливо якщо ви використовуєте їх атрибут: NotNullAttribute

Існує "Код контрактів Microsoft", де ви використовуєте синтаксис, Contract.Requires(obj != null)який дає вам час виконання та перевірку компіляції: Введення кодових контрактів .

Також є "PostSharp", який дозволить вам просто використовувати такі атрибути:

public void DoSometing([NotNull] obj)

Роблячи це і зробивши PostSharp частиною вашого процесу збирання obj, перевірятиметься на нуль під час виконання. Див.: Нульова перевірка PostSharp

Простий код рішення

Або ви завжди можете кодувати свій власний підхід, використовуючи звичайний старий код. Наприклад, ось структура, яку ви можете використовувати для лову нульових посилань. Він моделюється за тією ж концепцією, що і Nullable<T>:

[System.Diagnostics.DebuggerNonUserCode]
public struct NotNull<T> where T: class
{
    private T _value;

    public T Value
    {
        get
        {
            if (_value == null)
            {
                throw new Exception("null value not allowed");
            }

            return _value;
        }
        set
        {
            if (value == null)
            {
                throw new Exception("null value not allowed.");
            }

            _value = value;
        }
    }

    public static implicit operator T(NotNull<T> notNullValue)
    {
        return notNullValue.Value;
    }

    public static implicit operator NotNull<T>(T value)
    {
        return new NotNull<T> { Value = value };
    }
}

Ви б використовували дуже схожий з тим самим способом, який ви використовували Nullable<T>, за винятком цілей, щоб досягти прямо протилежного - не допустити null. Ось кілька прикладів:

NotNull<Person> person = null; // throws exception
NotNull<Person> person = new Person(); // OK
NotNull<Person> person = GetPerson(); // throws exception if GetPerson() returns null

NotNull<T>непрямо додається до та з нього, Tтому ви можете використовувати його майже де завгодно. Наприклад, ви можете передати Personоб'єкт методу, який займає NotNull<Person>:

Person person = new Person { Name = "John" };
WriteName(person);

public static void WriteName(NotNull<Person> person)
{
    Console.WriteLine(person.Value.Name);
}

Як ви бачите вище, як з нульовим, ви отримаєте доступ до базового значення через Valueвластивість. Крім того, ви можете використовувати явний або неявний формат, ви можете побачити приклад із зворотним значенням нижче:

Person person = GetPerson();

public static NotNull<Person> GetPerson()
{
    return new Person { Name = "John" };
}

Або ви навіть можете використовувати його, коли метод просто повертається T(у цьому випадку Person), виконуючи акторський склад . Наприклад, наступний код точно сподобається коду вище:

Person person = (NotNull<Person>)GetPerson();

public static Person GetPerson()
{
    return new Person { Name = "John" };
}

Комбінувати з розширенням

Поєднайте NotNull<T>з методом розширення, і ви зможете охопити ще більше ситуацій. Ось приклад того, як може виглядати метод розширення:

[System.Diagnostics.DebuggerNonUserCode]
public static class NotNullExtension
{
    public static T NotNull<T>(this T @this) where T: class
    {
        if (@this == null)
        {
            throw new Exception("null value not allowed");
        }

        return @this;
    }
}

Ось приклад того, як це можна було використовувати:

var person = GetPerson().NotNull();

GitHub

Для довідки я зробив доступний вище код на GitHub, його можна знайти за адресою:

https://github.com/luisperezphd/NotNull

Пов'язана мова

C # 6.0 представив "нульового умовного оператора", який трохи допомагає в цьому. За допомогою цієї функції ви можете посилатися на вкладені об'єкти, і якщо будь-який з них є, nullвесь вираз повертається null.

Це зменшує кількість нульових перевірок, які вам доведеться зробити в деяких випадках. Синтаксис повинен ставити знак питання перед кожною крапкою. Візьмемо для прикладу наступний код:

var address = country?.State?.County?.City;

Уявіть, що countryце об'єкт типу, Countryякий має властивість називається Stateтощо. Якщо country, State, Countyабо Cityце nullте address will beнульовий . Therefore you only have to check whetherадресу isnull`.

Це відмінна функція, але вона дає менше інформації. Це не дає зрозуміти, який з 4 є нульовим.

Вбудований як Nullable?

У C # є приємна стенограма Nullable<T>, ви можете зробити щось незмінне, поставивши знак питання після такого типу int?.

Було б добре , якби C # було що - щось на зразок NotNull<T>структури вище і мав подібну стенографії, може бути знаком оклику , так що ви могли б написати що - щось на зразок (!) public void WriteName(Person! person).


2
Ніколи не кидайте NullReferenceException
Джон Сондерс

@JohnSaunders насмілююсь запитати, чому? (Серйозно, хоча чому?)
Луїс Перес

2
NullReferenceException призначений для викидання CLR. Це означає, що відбулося посилання на нуль. Це не означає, що посилання на нуль відбудеться за винятком того, що ви спритно перевірили спочатку.
Джон Сондерс

Я бачу ваш погляд на те, як це буде заплутано. Я оновив його до регулярного винятку для цього прикладу та спеціального винятку в GitHub.
Луїс Перес

Чудова відповідь на таке основне питання. Це не так вже й погано, коли ваш код виходить з ладу. Це жахливо, коли надходить з глибокої всередині комерційної бібліотеки третьої сторони, на яку ви покладаєтесь, і служба підтримки постійно наполягає на тому, що це повинен бути ваш код, який викликає проблему. І ви не зовсім впевнені, що це не так, і весь проект грунтується на зупинці .. Я насправді думаю, що це може зробити відповідну епітафію для моїх надгробних плит: "Посилання на об'єкт не встановлено на екземпляр об'єкта".
Даррел Лі

10

Цікаво, що жодна з відповідей на цій сторінці не згадує два крайні випадки, сподіваюся, ніхто не подумає, якщо я додам їх:

Випадок краю №1: паралельний доступ до словника

Загальні словники в .NET не є безпечними для потоків, і іноді вони можуть викинути NullReferenceабо навіть (частіше) a, KeyNotFoundExceptionколи ви намагаєтесь отримати доступ до ключа з двох одночасних потоків. Виняток є досить оманливим у цьому випадку.

Корпус №2: небезпечний код

Якщо a NullReferenceExceptionзакидається unsafeкодом, ви можете подивитися на змінні вказівника та перевірити їх на наявність IntPtr.Zeroчи щось. Це те саме ("виняток з нульового вказівника"), але в небезпечному коді змінні часто переносяться на типи значень / масиви тощо, і ти б'єш головою об стіну, цікавлячись, як тип типу може кинути це виняток.

(Ще одна причина невикористання небезпечного коду, якщо він вам, до речі, не потрібен)


5
Ваш приклад словника не є кращим. Якщо об'єкт не є безпечним для потоків, то його використання з декількох потоків дає випадкові результати. Ваш приклад небезпечного коду відрізняється від nullтого, чим?
Джон Сондерс

10

Ви можете виправити NullReferenceException чистим способом, використовуючи Null-conditional Operators в c # 6 і написати менше коду для обробки нульових перевірок.

Він використовується для тестування на null перед виконанням операції доступу члена (?.) Або індексу (? [).

Приклад

  var name = p?.Spouse?.FirstName;

еквівалентно:

    if (p != null)
    {
        if (p.Spouse != null)
        {
            name = p.Spouse.FirstName;
        }
    }

Результатом є те, що ім'я буде нульовим, коли p є нульовим або коли p.Spouse є null.

В іншому випадку імені змінної буде призначено значення p.Spouse.FirstName.

Докладніше: Нульові умовні оператори


9

Рядок помилки "Посилання на об'єкт не встановлено для екземпляра об'єкта". Вказується, що ви не присвоїли об'єкту екземпляра посилання на об'єкт, і ви все ще отримуєте доступ до операцій / методів цього об'єкта.

наприклад: скажімо, у вас є клас під назвою myClass і він містить одну властивість prop1.

public Class myClass
{
   public int prop1 {get;set;}
}

Тепер ви отримуєте доступ до цієї prop1 в іншому класі, як нижче:

public class Demo
{
     public void testMethod()
     {
        myClass ref = null;
        ref.prop1 = 1;  //This line throws error
     }
}

вище рядок видає помилку, оскільки посилання класу myClass оголошено, але не є екземпляром, або екземпляр об'єкта не присвоюється референу цього класу.

Для виправлення цього потрібно інстанціювати (призначити об'єкт посиланням на цей клас).

public class Demo
{
     public void testMethod()
     {
        myClass ref = null;
        ref = new myClass();
        ref.prop1 = 1;  
     }
}

4

NullReferenceException або посилання на Object, не встановлений для екземпляра об'єкта, виникає, коли об'єкт класу, який ви намагаєтеся використовувати, не є екземпляром. Наприклад:

Припустимо, що у вас є клас на ім'я Студент.

public class Student
{
    private string FirstName;
    private string LastName;
    public string GetFullName()
    {
        return FirstName + LastName;
    }
}

Тепер розглянемо інший клас, де ви намагаєтеся отримати повне ім’я учня.

public class StudentInfo
{      
    public string GetStudentName()
    {
        Student s;
        string fullname = s.GetFullName();
        return fullname;
    }        
}

Як видно з наведеного вище коду, висловлювання Student s - тільки декларує змінну типу Student, зауважте, що клас Student не є екземпляром на даний момент. Отже, коли оператор s.GetFullName () буде виконаний, він викине NullReferenceException.


3

Ну, простіше кажучи:

Ви намагаєтеся отримати доступ до об'єкта, який не створений або наразі не є в пам'яті.

Отже, як вирішити це:

  1. Налагоджуйте і нехай лампочка зламається ... Це безпосередньо переведе вас до змінної, яка зламана ... Тепер ваше завдання - це просто виправити це .. Використання нового ключового слова у відповідному місці.

  2. Якщо він викликаний деякими командами бази даних, оскільки об'єкт відсутній, то все, що вам потрібно зробити, - це зробити нульову перевірку та обробити її:

    if (i == null) {
        // Handle this
    }
  3. Найскладніше .. якщо GC вже зібрав об’єкт ... Це, як правило, відбувається, якщо ви намагаєтесь знайти об’єкт за допомогою рядків ... Тобто, знаходження його за назвою об'єкта, то може статися, що GC може вже Почистили його ... Це важко знайти і стане досить проблемою ... Кращий спосіб вирішити це - зробити нульові перевірки там, де це необхідно в процесі розробки. Це заощадить вам багато часу.

Знаходячись по імені, я маю на увазі, що деякі рамки дозволяють вам FIndObjects за допомогою рядків і код може виглядати так: FindObject ("ObjectName");


3
Якщо у вас є посилання на об'єкт, то GC ніколи його не очищає
Джон Сондерс

2
якщо ви використовуєте такі речі, як FindObject ("Ім'я об'єкта"), немає жодного способу GC дізнається заздалегідь, що ви збираєтесь посилатись на цей об'єкт. Це те, що намагається пояснити. Це відбувається під час виконання
Акаш Гута

2
Є деякі рамки, які забезпечують цю функціональність у C #, наприклад, Unity. питання не має нічого спільного з BCl. Шукайте в Інтернеті, перш ніж критикувати, є безліч функцій на кшталт них, і для ур доброї інформації я навіть використовую її щодня. А тепер скажіть, будь ласка, як ця відповідь не дає сенсу.
Акаш Гута

2
docs.unity3d.com/ScriptReference/… перевірити посилання та виправити urself mr.expert: p
Акаш Гутха

Приклади, які я бачив у вашому посиланні, присвоюють результати GameObject.Find в полі учасника. Це посилання, і GC не збиратиме його, поки не буде зібраний об'єкт, що містить його.
Джон Сондерс

1

Буквально найпростіший спосіб виправити NullReferenceExeption має два способи. Якщо у вас є GameObject, наприклад, із доданим сценарієм та змінною з назвою rb (rigidbody), ця змінна запуститься нульовою при запуску гри.
Ось чому ви отримуєте NullReferenceExeption, оскільки на комп'ютері немає даних, що зберігаються в цій змінній.

Я буду використовувати приклад змінної RigidBody.
Ми можемо додавати дані дуже легко насправді кількома способами:

  1. Додати Rigidbody до об'єкта з AddComponent> Фізика> Rigidbody
    Потім перейдіть в сценарій і введіть rb = GetComponent<Rigidbody>();
    Цей рядок коду найкраще працює під вашими Start()або Awake()функцій.
  2. Ви можете додати компонент програмно і одночасно призначити змінну одним рядком коду: rb = AddComponent<RigidBody>();

Подальші примітки: Якщо ви хочете, щоб єдність додавала компонент до вашого об'єкта, і ви, можливо, забули додати його, ви можете набрати [RequireComponent(typeof(RigidBody))]вище декларації свого класу (пробіл під усіма вашими умовами).
Насолоджуйтесь і отримуйте задоволення, роблячи ігри!


-1

Якщо ми розглядаємо загальні сценарії, коли цей виняток можна кинути, отримуючи доступ до властивостей, розташованих у верхній частині об'єкта.

Наприклад:

string postalcode=Customer.Address.PostalCode; 
//if customer or address is null , this will through exeption

тут, якщо адреса нульова, ви отримаєте NullReferenceException.

Отже, як практика, ми завжди повинні використовувати нульову перевірку, перш ніж отримувати доступ до властивостей таких об'єктів (особливо в загальних)

string postalcode=Customer?.Address?.PostalCode;
//if customer or address is null , this will return null, without through a exception

-3

Це в основному є виключенням з нульовою посиланням . Як заявляє Microsoft,

Виняток NullReferenceException видається при спробі доступу до члена типу, значення якого є null.

Що це означає?

Це означає, що якщо будь-який член, який не має жодного значення, і ми змушуємо його виконати певне завдання, система, безперечно, кине повідомлення та скаже-

"Привіт, чекай, цей член не має значень, тому він не може виконувати завдання, яке ти йому передаєш".

Саме виняток говорить про те, що щось передається, але значення якого не встановлюється. Отже, це означає, що це відбувається лише під час використання еталонних типів, оскільки типи значень не підлягають нулюванню.

NullReferenceException не відбудеться, якщо ми використовуємо члени типу Значення.

class Program
{
    static void Main(string[] args)
    {
        string str = null;
        Console.WriteLine(str.Length);
        Console.ReadLine();
    }
}

Наведений вище код показує просту рядок, який присвоюється символу a нульове значення.

Тепер, коли я намагаюся надрукувати довжину рядка str , я отримую необроблений виняток типу 'System.NullReferenceException' оскільки str str вказує на null, а довжина null не може бути.

' NullReferenceException " також виникає, коли ми забуваємо інстанціювати тип посилання.

Припустимо, у мене є метод класу та члена. Я не створював моменти свого класу, а лише назвав свій клас. Тепер, якщо я спробую скористатися методом, компілятор видасть помилку або видасть попередження (залежно від компілятора).

class Program
{
    static void Main(string[] args)
    {
        MyClass1 obj;
        obj.foo();  //Use of unassigned local variable 'obj'
    }
}

public class MyClass1
{
    internal void foo()
    {
        Console.WriteLine("hello from foo");

    }
}

Компілятор для наведеного вище коду викликає помилку, що змінна obj не призначена, що означає, що наша змінна має нульові значення або нічого. Компілятор для наведеного вище коду викликає помилку цієї змінної obj не призначена, що означає, що наша змінна має нульові значення або нічого.

Чому це відбувається?

  • NullReferenceException виникає з нашої вини в тому, що ми не перевірили значення об'єкта. У розробці коду ми часто залишаємо неперевірені значення об'єкта.

  • Це також виникає, коли ми забуваємо інстанціювати свої об'єкти. Використання методів, властивостей, колекцій тощо, які можуть повернути або встановити нульові значення, також може бути причиною цього винятку.

Як цього можна уникнути?

Існують різні способи та методи уникнути цього відомого винятку:

  1. Явна перевірка: Ми повинні дотримуватися традиції перевірки об'єктів, властивостей, методів, масивів та колекцій на предмет наявності їх. Це можна просто реалізувати, використовуючи умовні оператори, такі як if-else if-else і т.д.

  2. Поводження з винятками: Один з важливих способів управління цим винятком. Використовуючи прості блоки "try-catch-нарешті", ми можемо контролювати цей виняток, а також вести журнал його. Це може бути дуже корисно, коли ваша заявка знаходиться на етапі виробництва.

  3. Нульові оператори: Оператор Null Coalescing та null умовні оператори також можуть бути корисними під час встановлення значень для об'єктів, змінних, властивостей та полів.

  4. Налагоджувач: Для розробників у нас є велика зброя налагодження. Якщо ми стикаємося з NullReferenceException під час розробки, ми можемо скористатися налагоджувачем, щоб дістатися до джерела винятку.

  5. Вбудований метод: системні методи, такі як GetValueOrDefault (), IsNullOrWhiteSpace () та IsNullorEmpty (), перевіряють наявність нулів і присвоюють значення за замовчуванням, якщо є нульове значення.

Тут вже багато хороших відповідей. Ви також можете перевірити більш детальний опис із прикладами в моєму блозі .

Сподіваюся, що це теж допомагає!


Ви в основному скопіювали половину цієї публікації в блозі і не додали нічого нового, що існуючі відповіді не адресують.
CodeCaster

@codecaster Це копіювання, коли ви переписуєте резюме з власного блогу. Я знаю, що у моїй відповіді немає нічого нового і нічого нового, що попередні відповіді не мають, але я хочу зробити свій внесок у більш витончений спосіб і дати можливість іншим зрозуміти те, як я зрозумів. Буде радий, навіть якщо це допоможе одній людині. Сумлінно.
Васим

-4

Якщо хтось отримує це повідомлення під час збереження або збирання збірки, просто закрийте всі файли, а потім відкрийте будь-який файл для компіляції та збереження.

Для мене причиною було те, що я перейменував файл і старий файл все ще був відкритий.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.