Câu hỏi NullReferenceException là gì và cách khắc phục?


Tôi có một số mã và khi nó thực hiện, nó ném một NullReferenceException, nói:

Tham chiếu đối tượng không được đặt thành một thể hiện của đối tượng.

Điều này có nghĩa là gì và tôi có thể làm gì để khắc phục lỗi này?


1878


gốc


Người trợ giúp ngoại lệ trong VS 2017 sẽ hữu ích hơn trong việc chẩn đoán nguyên nhân của ngoại lệ này - blogs.msdn.microsoft.com/visualstudio/2016/11/28/… Dưới Trình trợ giúp ngoại lệ mới. - Zev Spitz
Có thể chúng ta chỉ nói "đối tượng chưa được khởi tạo"? Vì vậy, nếu bạn có một tuyên bố biến: SomeClass myVariable; điều này sẽ tạo myVariable với tham chiếu đến SomeClass, nhưng nó không được khởi tạo và sẽ bằng null. Bạn phải gọi khởi tạo lớp bằng cách thực hiện SomeClass myVariable = new SomeClass (); Hoặc, nếu bạn đang trả về một tham chiếu đến một biến khác: SomeClass myVariable = anotherVariableDeclared (); - Arvin Amir
@Arvin những gì về `MfClass - John Saunders
Kính gửi khách truy cập trong tương lai, câu trả lời cho câu hỏi này đều áp dụng cho ArgumentNullException. Nếu câu hỏi của bạn đã bị đóng dưới dạng bản sao của câu hỏi này và bạn đang gặp ANE, vui lòng làm theo hướng dẫn trong câu trả lời để gỡ lỗi và khắc phục sự cố của bạn. - Will
@will ANE chỉ nên xảy ra nếu một giá trị rỗng được chuyển thành tham số. Bạn có thể đưa ra một ví dụ nếu câu hỏi ANE được đóng dưới dạng bản sao của câu hỏi này không? - John Saunders


Các câu trả lời:


Nguyên nhân là gì?

Điểm mấu chốt

Bạn đang cố gắng sử dụng cái gì đó null (hoặc là Nothing trong VB.NET). Điều này có nghĩa là bạn có thể đặt null, hoặc bạn không bao giờ đặt nó vào bất cứ điều gì cả.

Giống như bất cứ điều gì khác, null được thông qua xung quanh. Nếu nó là null  trong phương thức "A", có thể là phương thức "B" đã chuyển null  đến phương pháp "A".

null có thể có ý nghĩa khác nhau:

  1. Các biến đối tượng - không được khởi tạo và do đó không có gì. Trong trường hợp này, nếu bạn truy cập các thuộc tính hoặc các phương thức của các đối tượng như vậy, nó gây ra một NullReferenceException.
  2. Nhà phát triển là sử dụng null cố ý để cho biết không có giá trị có ý nghĩa. Lưu ý rằng C # có khái niệm về kiểu dữ liệu nullable cho các biến (như các bảng cơ sở dữ liệu có thể có các trường nullable) - bạn có thể gán null để họ cho biết không có giá trị nào được lưu trữ trong đó, ví dụ int? a = null; nơi dấu chấm hỏi cho biết nó được phép lưu trữ null trong biến a. Bạn có thể kiểm tra xem bằng if (a.HasValue) {...} Hoặc với if (a==null) {...}. Các biến dễ vỡ, như a ví dụ này, cho phép truy cập giá trị qua a.Value một cách rõ ràng, hoặc giống như bình thường qua a.
    chú thích truy cập thông qua a.Value ném một InvalidOperationException Thay vì một NullReferenceException nếu a Là null - bạn nên thực hiện kiểm tra trước, tức là nếu bạn có biến số không thể vô hiệu khác int b; thì bạn nên làm bài tập như if (a.HasValue) { b = a.Value; } hoặc ngắn hơn if (a != null) { b = a; }.

Phần còn lại của bài viết này đi vào chi tiết hơn và cho thấy những sai lầm mà nhiều lập trình viên thường thực hiện có thể dẫn đến NullReferenceException.

Cụ thể hơn

Thời gian chạy ném một NullReferenceException  luôn luôn có nghĩa là điều tương tự: bạn đang cố gắng sử dụng tham chiếu và tham chiếu không được khởi tạo (hoặc là Một lần khởi tạo, nhưng là không còn nữa được khởi tạo).

Điều này có nghĩa là tham chiếu là nullvà bạn không thể truy cập các thành viên (chẳng hạn như các phương thức) thông qua một null tài liệu tham khảo. Trường hợp đơn giản nhất:

string foo = null;
foo.ToUpper();

Điều này sẽ ném một NullReferenceException ở dòng thứ hai bởi vì bạn không thể gọi phương thức cá thể ToUpper() trên một string tham chiếu trỏ đến null.

Gỡ lỗi

Làm thế nào để bạn tìm thấy nguồn gốc của một NullReferenceException? Ngoài việc xem xét ngoại lệ, nó sẽ được ném chính xác tại vị trí mà nó xảy ra, các quy tắc chung của gỡ lỗi trong Visual Studio áp dụng: đặt các điểm ngắt chiến lược và kiểm tra các biến của bạn, hoặc bằng cách di chuột qua tên của họ, mở cửa sổ Xem nhanh (Quick) hoặc sử dụng các bảng gỡ lỗi khác nhau như Người dân địa phương và Ô tô.

Nếu bạn muốn tìm nơi tham chiếu hoặc không được đặt, hãy nhấp chuột phải vào tên của nó và chọn "Tìm tất cả tài liệu tham khảo". Sau đó, bạn có thể đặt điểm ngắt tại mọi vị trí đã tìm thấy và chạy chương trình của bạn với trình gỡ lỗi được đính kèm. Mỗi khi trình gỡ lỗi phá vỡ một điểm ngắt như vậy, bạn cần phải xác định xem bạn có mong đợi tham chiếu không rỗng, kiểm tra biến và xác minh rằng nó trỏ đến một cá thể khi bạn mong đợi nó.

Bằng cách làm theo luồng chương trình theo cách này, bạn có thể tìm thấy vị trí mà cá thể không được rỗng và tại sao nó không được đặt đúng.

Ví dụ

Một số trường hợp phổ biến nơi có thể ném ngoại lệ:

Chung

ref1.ref2.ref3.member

Nếu ref1 hoặc ref2 hoặc ref3 là null, thì bạn sẽ nhận được NullReferenceException. Nếu bạn muốn giải quyết vấn đề, sau đó tìm ra cái nào là null bằng cách viết lại biểu thức cho nó tương đương đơn giản hơn:

var r1 = ref1;
var r2 = r1.ref2;
var r3 = r2.ref3;
r3.member

Cụ thể, trong HttpContext.Current.User.Identity.Name, các HttpContext.Current có thể là null hoặc User tài sản có thể là null hoặc Identity tài sản có thể là null.

gián tiếp

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.
    }
}

Nếu bạn muốn tránh tham chiếu con (Person) null, bạn có thể khởi tạo nó trong hàm tạo của đối tượng cha (Book).

Trình khởi tạo đối tượng lồng nhau

Điều tương tự cũng áp dụng cho bộ khởi tạo đối tượng lồng nhau:

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

Điều này dịch sang

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

Trong khi new từ khóa được sử dụng, nó chỉ tạo ra một thể hiện mới Book, nhưng không phải là một trường hợp mới Person, nên Author tài sản vẫn còn null.

Trình khởi tạo bộ sưu tập lồng nhau

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

Bộ khởi tạo bộ sưu tập lồng nhau hoạt động giống nhau:

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

Điều này dịch sang

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

Các new Person chỉ tạo ra một thể hiện Person, nhưng Books bộ sưu tập vẫn còn null. Cú pháp khởi tạo bộ sưu tập không tạo bộ sưu tập cho p1.Books, nó chỉ dịch sang p1.Books.Add(...) các câu lệnh.

Mảng

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

Các phần tử mảng

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.

Mảng răng cưa

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.

Bộ sưu tập / Danh sách / Từ điển

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

Biến phạm vi (gián tiếp / trì hoãn)

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.

Sự kiện

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
    }
}

Quy ước đặt tên không hợp lệ:

Nếu bạn đặt tên các trường khác với người dân địa phương, bạn có thể đã nhận ra rằng bạn chưa bao giờ khởi tạo trường.

public class Form1 {
    private Customer customer;

    private void Form1_Load(object sender, EventArgs e) {
        Customer customer = new Customer();
        customer.Name = "John";
    }

    private void Button_Click(object sender, EventArgs e) {
        MessageBox.Show(customer.Name);
    }
}

Điều này có thể được giải quyết bằng cách theo quy ước đến các trường tiền tố có dấu gạch dưới:

private Customer _customer;

Vòng đời trang 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!";
    }
}

Giá trị phiên ASP.NET

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

Mô hình xem trống của ASP.NET MVC

Nếu ngoại lệ xảy ra khi tham chiếu thuộc tính của @Model trong chế độ xem ASP.NET MVC, bạn cần hiểu rằng Model được đặt trong phương thức hành động của bạn, khi bạn return một cái nhìn. Khi bạn trả về một mô hình rỗng (hoặc thuộc tính mô hình) từ trình điều khiển của bạn, ngoại lệ xảy ra khi các khung nhìn truy cập nó:

// 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 -->

Thứ tự và sự kiện tạo điều khiển WPF

Điều khiển WPF được tạo trong khi gọi đến InitializeComponent theo thứ tự chúng xuất hiện trong cây thị giác. A NullReferenceException sẽ được nâng lên trong trường hợp các điều khiển được tạo sớm với các trình xử lý sự kiện, v.v. InitializeComponent tham chiếu đến các điều khiển được tạo muộn.

Ví dụ :

<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>

Đây comboBox1 được tạo trước label1. Nếu comboBox1_SelectionChanged cố gắng tham chiếu `label1, nó sẽ chưa được tạo.

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

Thay đổi thứ tự của các khai báo trong XAML (tức là, danh sách label1 trước comboBox1, bỏ qua các vấn đề của triết lý thiết kế, ít nhất sẽ giải quyết NullReferenceException đây.

Cast with as

var myThing = someObject as Thing;

Điều này không ném một InvalidCastException nhưng trả về một null khi diễn viên thất bại (và khi someObject là chính nó null). Vì vậy, hãy nhận biết điều đó.

LINQ FirstOrDefault () và SingleOrDefault ()

Các phiên bản đơn giản First() và Single() ném ngoại lệ khi không có gì. Các phiên bản "OrDefault" trả về null trong trường hợp đó. Vì vậy, hãy nhận biết điều đó.

cho mỗi

foreach ném khi bạn cố gắng lặp lại bộ sưu tập rỗng. Thường do bất ngờ null kết quả từ các phương thức trả về bộ sưu tập.

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

Ví dụ thực tế hơn - chọn các nút từ tài liệu XML. Sẽ ném nếu không tìm thấy các nút nhưng gỡ lỗi ban đầu cho thấy rằng tất cả các thuộc tính đều hợp lệ:

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

Các cách tránh

Kiểm tra rõ ràng null và bỏ qua các giá trị null.

Nếu bạn mong đợi tham chiếu đôi khi không có giá trị, bạn có thể kiểm tra xem nó có đang null trước khi truy cập các thành viên cá thể:

void PrintName(Person p) {
    if (p != null) {
        Console.WriteLine(p.Name);
    }
}

Kiểm tra rõ ràng null và cung cấp giá trị mặc định.

Các phương thức gọi bạn mong đợi để trả về một thể hiện có thể trả về null, ví dụ như khi không tìm thấy đối tượng. Bạn có thể chọn trả lại giá trị mặc định khi trường hợp này xảy ra:

string GetCategory(Book b) {
    if (b == null)
        return "Unknown";
    return b.Category;
}

Kiểm tra rõ ràng null từ các cuộc gọi phương thức và ném một ngoại lệ tùy chỉnh.

Bạn cũng có thể ném một ngoại lệ tùy chỉnh, chỉ để bắt nó trong mã gọi:

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;
}

Sử dụng Debug.Assert nếu một giá trị không bao giờ nên null, để nắm bắt vấn đề sớm hơn ngoại lệ xảy ra.

Khi bạn biết trong quá trình phát triển, một phương pháp có thể có thể, nhưng không bao giờ nên quay trở lại null, bạn có thể dùng Debug.Assert() để phá vỡ càng sớm càng tốt khi nó xảy ra:

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.
}

Mặc dù kiểm tra này sẽ không kết thúc trong bản phát hành bản phát hành của bạn, khiến nó ném NullReferenceException một lần nữa khi book == null tại thời gian chạy ở chế độ phát hành.

Sử dụng GetValueOrDefault() cho các loại giá trị rỗng để cung cấp giá trị mặc định khi chúng 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

Sử dụng toán tử kết hợp rỗng: ?? [C #] hoặc If() [VB].

Cách viết tắt để cung cấp giá trị mặc định khi null gặp phải:

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;
}

Sử dụng toán tử điều kiện null: ?. hoặc là ?[x] cho các mảng (có sẵn trong C # 6 và VB.NET 14):

Điều này đôi khi cũng được gọi là điều hướng an toàn hoặc nhà điều hành Elvis (sau hình dạng của nó). Nếu biểu thức ở phía bên trái của toán tử là null, thì bên phải sẽ không được đánh giá, và null được trả về thay thế. Điều đó có nghĩa là các trường hợp như thế này:

var title = person.Title.ToUpper();

Nếu người đó không có tiêu đề, điều này sẽ ném một ngoại lệ vì nó đang cố gắng gọi ToUpper trên một thuộc tính có giá trị null.

Trong C # 5 và dưới đây, điều này có thể được bảo vệ bằng:

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

Bây giờ biến tiêu đề sẽ là null thay vì ném một ngoại lệ. C # 6 giới thiệu một cú pháp ngắn hơn cho điều này:

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

Điều này sẽ dẫn đến biến tiêu đề đang được nullvà cuộc gọi đến ToUpper không được thực hiện nếu person.Title Là null.

Tất nhiên, bạn vẫn phải kiểm tra title cho null hoặc sử dụng toán tử điều kiện null cùng với toán tử kết hợp null (??) để cung cấp giá trị mặc định:

// 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;

Tương tự, đối với mảng bạn có thể sử dụng ?[i] như sau:

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

Điều này sẽ làm như sau: Nếu myIntArray là null, biểu thức trả về null và bạn có thể kiểm tra an toàn nó. Nếu nó chứa một mảng, nó sẽ làm tương tự như: elem = myIntArray[i]; và trả về ith thành phần.

Các kỹ thuật đặc biệt để gỡ lỗi và sửa các derefs rỗng trong vòng lặp

C # hỗ trợ "khối lặp" (được gọi là "máy phát điện" trong một số ngôn ngữ phổ biến khác). Null dereference ngoại lệ có thể đặc biệt khó khăn để gỡ lỗi trong các khối lặp do trì hoãn thực thi:

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) { ... }

Nếu whatever kết quả trong null sau đó MakeFrob sẽ ném. Bây giờ, bạn có thể nghĩ rằng điều đúng đắn cần làm là:

// 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();
}

Tại sao điều này sai? Vì khối lặp không thực sự chạy cho đên khi foreach! Cuộc gọi đến GetFrobs chỉ đơn giản trả về một đối tượng khi được lặp lại sẽ chạy khối lặp.

Bằng cách viết một kiểm tra null như thế này, bạn ngăn chặn các dereference null, nhưng bạn di chuyển ngoại lệ đối số null đến điểm của sự lặp lại, không phải đến điểm của gọi điện, và đó là rất khó hiểu.

Sửa chữa chính xác là:

// 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();
}

Tức là, tạo một phương thức trợ giúp riêng có logic khối lặp, và phương thức bề mặt công khai thực hiện kiểm tra rỗng và trả về trình lặp. Bây giờ thì ở đâu GetFrobs được gọi, kiểm tra null sẽ xảy ra ngay lập tức và sau đó GetFrobsForReal thực hiện khi chuỗi được lặp lại.

Nếu bạn kiểm tra nguồn tham chiếu cho LINQ to Objects, bạn sẽ thấy rằng kỹ thuật này được sử dụng trong suốt. Đó là hơi clunky để viết, nhưng nó làm cho gỡ lỗi lỗi vô hiệu dễ dàng hơn nhiều. Tối ưu hóa mã của bạn để thuận tiện cho người gọi, chứ không phải sự tiện lợi của tác giả.

Lưu ý về các tham chiếu rỗng trong mã không an toàn

C # có chế độ "không an toàn", như tên của nó, cực kỳ nguy hiểm vì các cơ chế an toàn bình thường cung cấp an toàn bộ nhớ và an toàn kiểu không được thực thi. Bạn không nên viết mã không an toàn trừ khi bạn có hiểu biết sâu sắc và sâu sắc về cách hoạt động của bộ nhớ.

Trong chế độ không an toàn, bạn nên biết hai sự kiện quan trọng:

  • dereferencing a null con trỏ tạo ra cùng ngoại lệ như dereferencing a null tài liệu tham khảo
  • dereferencing một con trỏ không null không hợp lệ có thể tạo ra ngoại lệ trong một số trường hợp

Để hiểu lý do tại sao, nó giúp hiểu cách .NET tạo ra ngoại lệ dereference null ở nơi đầu tiên. (Các chi tiết này áp dụng cho .NET đang chạy trên Windows; các hệ điều hành khác sử dụng các cơ chế tương tự.)

Bộ nhớ được ảo hóa trong Windows; mỗi quá trình nhận được một không gian bộ nhớ ảo của nhiều "trang" của bộ nhớ được theo dõi bởi hệ điều hành. Mỗi trang của bộ nhớ có các cờ được đặt trên đó, nó xác định cách nó có thể được sử dụng: đọc từ, được viết tới, được thực thi, v.v. Các thấp nhất trang được đánh dấu là "sản xuất một lỗi nếu bao giờ được sử dụng trong bất kỳ cách nào".

Cả một con trỏ null và tham chiếu null trong C # được biểu diễn nội bộ là số không, và vì vậy bất kỳ nỗ lực nào để dereference nó vào bộ nhớ tương ứng của nó đều làm cho hệ điều hành tạo ra lỗi. Thời gian chạy .NET sau đó phát hiện lỗi này và biến nó thành ngoại lệ dereference null.

Đó là lý do tại sao dereferencing cả một con trỏ null và một tham chiếu null tạo ra cùng một ngoại lệ.

Điều gì về điểm thứ hai? Dereferencing bất kì con trỏ không hợp lệ rơi vào trang thấp nhất của bộ nhớ ảo gây ra lỗi hệ điều hành giống nhau, và do đó cùng một ngoại lệ.

Tại sao điều này có ý nghĩa? Vâng, giả sử chúng ta có một cấu trúc chứa hai int và một con trỏ không được quản lý bằng null. Nếu chúng ta cố gắng dereference int thứ hai trong cấu trúc, CLR sẽ không cố gắng truy cập vào bộ nhớ tại vị trí 0; nó sẽ truy cập bộ nhớ tại vị trí thứ tư. Nhưng một cách hợp lý, đây là một sự thiếu thận trọng vì chúng ta đang đi đến địa chỉ đó thông qua null.

Nếu bạn đang làm việc với mã không an toàn và bạn nhận được một ngoại lệ dereference null, chỉ cần lưu ý rằng con trỏ vi phạm không cần phải rỗng. Nó có thể là bất kỳ vị trí nào trong trang thấp nhất và ngoại lệ này sẽ được tạo ra.


2111



Có lẽ đây là một nhận xét câm nhưng không phải là cách đầu tiên và tốt nhất để tránh vấn đề này là để khởi tạo đối tượng? Đối với tôi nếu lỗi này xảy ra thường là vì tôi quên khởi tạo một cái gì đó giống như phần tử mảng. Tôi nghĩ rằng nó ít phổ biến hơn để xác định đối tượng là null và sau đó tham chiếu nó. Có thể đưa ra cách giải quyết từng vấn đề tiếp giáp với mô tả. Vẫn là một bài tốt. - JPK
Điều gì sẽ xảy ra nếu không có đối tượng, mà là giá trị trả về từ một phương thức hoặc thuộc tính? - John Saunders
Cuốn sách / tác giả ví dụ là một chút lạ .... Làm thế nào mà thậm chí biên dịch? Intellisense thậm chí hoạt động như thế nào? Điều này là gì tôi không tốt với computar ... - Will
@ Will: chỉnh sửa cuối cùng của tôi có giúp ích gì không? Nếu không, vui lòng nêu rõ hơn về những gì bạn thấy là sự cố. - John Saunders
@ JohnSaunders Oh, không, xin lỗi, tôi có nghĩa là phiên bản khởi tạo đối tượng của điều đó. new Book { Author = { Age = 45 } }; Làm thế nào để khởi tạo bên trong ngay cả ... Tôi không thể nghĩ về một tình huống mà init bên trong sẽ bao giờ làm việc, nhưng nó biên dịch và intellisense hoạt động ... Trừ khi cho các cấu trúc? - Will


Ngoại lệ NullReference - Visual Basic

Các NullReference Exception cho Ngôn ngữ lập trình không khác gì cái ở C #. Sau khi tất cả, họ đều báo cáo cùng một ngoại lệ được định nghĩa trong Khuôn khổ .NET mà cả hai đều sử dụng. Nguyên nhân duy nhất cho Visual Basic là hiếm (có lẽ chỉ có một).

Câu trả lời này sẽ sử dụng các thuật ngữ, cú pháp và ngữ cảnh của Visual Basic. Các ví dụ được sử dụng đến từ một số lượng lớn các câu hỏi về Stack Overflow trước đây. Điều này là để tối đa hóa mức độ liên quan bằng cách sử dụng các loại các tình huống thường thấy trong bài đăng. Một chút giải thích thêm cũng được cung cấp cho những người có thể cần nó. Một ví dụ tương tự như của bạn là rất có khả năng được liệt kê ở đây.

Chú thích:

  1. Đây là khái niệm dựa trên: không có mã để bạn dán vào dự án của bạn. Nó được thiết kế để giúp bạn hiểu những gì gây ra một NullReferenceException (NRE), cách tìm, cách khắc phục và cách tránh nó. Một NRE có thể gây ra nhiều cách vì vậy đây không phải là cuộc gặp gỡ duy nhất của bạn.
  2. Các ví dụ (từ bài đăng Stack Overflow) không phải lúc nào cũng hiển thị cách tốt nhất để làm điều gì đó ngay từ đầu.
  3. Thông thường, cách khắc phục đơn giản nhất được sử dụng.

Ý nghĩa cơ bản

Thông điệp "Đối tượng không được đặt thành một thể hiện của đối tượng" có nghĩa là bạn đang cố gắng sử dụng một đối tượng chưa được khởi tạo. Đây là một trong những điều sau:

  • Ma cua ban khai báo một biến đối tượng, nhưng nó không khởi tạo nó (tạo một cá thể hoặc 'khởi tạo'nó)
  • Một cái gì đó mà mã của bạn giả định sẽ khởi tạo một đối tượng, không
  • Có thể, mã khác bị vô hiệu hóa sớm một đối tượng vẫn đang được sử dụng

Tìm nguyên nhân

Vì vấn đề là một tham chiếu đối tượng Nothing, câu trả lời là kiểm tra chúng để tìm ra cái nào. Sau đó, xác định lý do tại sao nó không được khởi tạo. Giữ chuột trên các biến khác nhau và Visual Studio (VS) sẽ hiển thị giá trị của chúng - thủ phạm sẽ là Nothing.

IDE debug display

Bạn cũng nên loại bỏ bất kỳ khối Thử / Bắt nào từ mã có liên quan, đặc biệt là những nơi không có gì trong khối Catch. Điều này sẽ làm cho mã của bạn gặp sự cố khi cố gắng sử dụng một đối tượng Nothing. Đây là cái bạn muốn bởi vì nó sẽ xác định chính xác vị trí của vấn đề và cho phép bạn xác định đối tượng gây ra sự cố đó.

A MsgBox trong Catch hiển thị Error while... sẽ giúp ích rất ít. Phương pháp này cũng dẫn đến rất tệ Stack Overflow câu hỏi, bởi vì bạn không thể mô tả các ngoại lệ thực tế, các đối tượng liên quan hoặc thậm chí dòng mã nơi nó xảy ra.

Bạn cũng có thể sử dụng Locals Window (Gỡ lỗi -> Windows -> Người dân địa phương) để kiểm tra đối tượng của bạn.

Một khi bạn biết những gì và nơi mà vấn đề là, nó thường là khá dễ dàng để sửa chữa và nhanh hơn so với gửi một câu hỏi mới.

Xem thêm:

Ví dụ và biện pháp khắc phục

Các đối tượng lớp / Tạo một cá thể

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

Vấn đề là ở đó Dim không tạo ra một CashRegister vật; nó chỉ khai báo một biến có tên reg Loại đó. Khai báo biến đối tượng và tạo ví dụ là hai thứ khác nhau.

Biện pháp khắc phục

Các New toán tử thường có thể được sử dụng để tạo cá thể khi bạn khai báo nó:

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

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

Khi nó chỉ thích hợp để tạo ra cá thể sau này:

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

Chú thích: Đừng sử dụng Dim một lần nữa trong một thủ tục, bao gồm cả hàm tạo (Sub New):

Private reg As CashRegister
'...

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

Điều này sẽ tạo ra địa phương biến, reg, chỉ tồn tại trong ngữ cảnh đó (phụ). Các reg biến với mức mô-đun Scope mà bạn sẽ sử dụng ở mọi nơi khác Nothing.

Thiếu New nhà điều hành là nguyên nhân số 1 của NullReference Exceptions đã thấy trong các câu hỏi Stack Overflow được xem xét.

Visual Basic cố gắng làm cho quá trình rõ ràng liên tục bằng cách sử dụng New: Sử dụng New Toán tử tạo ra một Mới đối tượng và cuộc gọi Sub New - hàm tạo - nơi đối tượng của bạn có thể thực hiện bất kỳ khởi tạo nào khác.

Để rõ ràng, Dim (hoặc là Private) chỉ có tuyên bố một biến và Type. Các Phạm vi của biến - cho dù nó tồn tại cho toàn bộ mô-đun / lớp hoặc là cục bộ cho một thủ tục - được xác định bởi Ở đâu nó được khai báo. Private | Friend | Public xác định cấp truy cập, không Phạm vi.

Để biết thêm thông tin, hãy xem:


Mảng

Mảng cũng phải được khởi tạo:

Private arr as String()

Mảng này chỉ được khai báo, không được tạo. Có một số cách để khởi tạo một mảng:

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) {}

Lưu ý: Bắt đầu với VS 2010, khi khởi tạo một mảng cục bộ bằng cách sử dụng chữ cái và Option Infer, các As <Type> và New các yếu tố là tùy chọn:

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}

Kiểu dữ liệu và kích thước mảng được suy ra từ dữ liệu được gán. Khai báo lớp / mô-đun vẫn yêu cầu As <Type> với Option Strict:

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

Ví dụ: Mảng các đối tượng lớp

Dim arrFoo(5) As Foo

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

Mảng đã được tạo, nhưng Foocác đối tượng trong đó thì không.

Biện pháp khắc phục

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

Sử dụng một List(Of T) sẽ làm cho nó khá khó khăn để có một phần tử mà không có một đối tượng hợp lệ:

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

Để biết thêm thông tin, hãy xem:


Danh sách và bộ sưu tập

Bộ sưu tập .NET (trong đó có nhiều giống - Danh sách, Từ điển, v.v.) cũng phải được tạo hoặc tạo ra.

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

Bạn nhận được cùng một ngoại lệ cho cùng một lý do - myList chỉ được khai báo, nhưng không có cá thể nào được tạo ra. Các biện pháp khắc phục là như nhau:

myList = New List(Of String)

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

Một giám sát chung là một lớp học sử dụng một bộ sưu tập 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

Một trong hai thủ tục sẽ dẫn đến một NRE, bởi vì barList chỉ được khai báo, không được khởi tạo. Tạo một thể hiện của Foo cũng sẽ không tạo ra một thể hiện của nội bộ barList. Nó có thể là ý định để làm điều này trong constructor:

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

Như trước đây, điều này là không chính xác:

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

Để biết thêm thông tin, hãy xem List(Of T) Lớp học.


Đối tượng cung cấp dữ liệu

Làm việc với cơ sở dữ liệu trình bày nhiều cơ hội cho một NullReference vì có thể có nhiều đối tượng (Command, Connection, Transaction, Dataset, DataTable, DataRows....) sử dụng cùng một lúc. Chú thích: Không quan trọng nhà cung cấp dữ liệu nào bạn đang sử dụng - MySQL, SQL Server, OleDB, v.v. - các khái niệm giống nhau.

ví dụ 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

Như trước đây, ds Đối tượng Dataset đã được khai báo, nhưng một cá thể không bao giờ được tạo ra. Các DataAdapter sẽ điền vào một hiện tại DataSet, không tạo ra một. Trong trường hợp này, kể từ ds là một biến cục bộ, IDE cảnh báo bạn điều này có thể xảy ra:

img

Khi được khai báo dưới dạng biến cấp mô-đun / lớp, dường như là trường hợp con, trình biên dịch không thể biết nếu đối tượng đã được tạo ra bởi một thủ tục thượng nguồn. Đừng bỏ qua cảnh báo.

Biện pháp khắc phục

Dim ds As New DataSet

Ví dụ 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)

Lỗi đánh máy là một vấn đề ở đây: Employees so với Employee. Không có DataTable có tên "Employee" được tạo ra, do đó, NullReferenceException kết quả cố gắng truy cập nó. Một vấn đề tiềm năng khác là giả định sẽ có Items có thể không như vậy khi SQL bao gồm mệnh đề WHERE.

Biện pháp khắc phục

Vì điều này sử dụng một bảng, sử dụng Tables(0) sẽ tránh lỗi chính tả. Kiểm tra Rows.Count cũng có thể giúp:

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 là hàm trả về số lượng Rows bị ảnh hưởng cũng có thể được kiểm tra:

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

Ví dụ 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

Các DataAdapter sẽ cung cấp TableNames như trong ví dụ trước, nhưng nó không phân tích các tên từ bảng SQL hoặc cơ sở dữ liệu. Kết quả là, ds.Tables("TICKET_RESERVATION") tham chiếu đến một bảng không tồn tại.

Các Biện pháp khắc phục là như nhau, tham khảo bảng theo chỉ mục:

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

Xem thêm Lớp DataTable.


Đường dẫn đối tượng / lồng nhau

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

Mã chỉ kiểm tra Items trong khi cả hai myFoo và Bar cũng có thể là Không có gì. Các biện pháp khắc phục là kiểm tra toàn bộ chuỗi hoặc đường dẫn của đối tượng cùng một lúc:

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

AndAlso là quan trọng. Các thử nghiệm tiếp theo sẽ không được thực hiện sau lần đầu tiên False tình trạng gặp phải. Điều này cho phép mã 'khoan' một cách an toàn vào (các) đối tượng một 'cấp' tại một thời điểm, đánh giá myFoo.Bar chỉ sau (và nếu) myFoo được xác định là hợp lệ. Chuỗi đối tượng hoặc đường dẫn có thể nhận được khá lâu khi mã hóa các đối tượng phức tạp:

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

Không thể tham khảo bất cứ thứ gì 'hạ lưu' của một null vật. Điều này cũng áp dụng cho các điều khiển:

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

Đây, myWebBrowser hoặc là Document có thể là Không có gì hoặc formfld1 phần tử có thể không tồn tại.


Điều khiển giao diện người dùng

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)

Trong số những thứ khác, mã này không dự đoán rằng người dùng có thể không chọn một cái gì đó trong một hoặc nhiều điều khiển giao diện người dùng. ListBox1.SelectedItem cũng có thể là Nothing, vì thế ListBox1.SelectedItem.ToString sẽ dẫn đến một NRE.

Biện pháp khắc phục

Xác thực dữ liệu trước khi sử dụng nó (cũng sử dụng Option Strict và các tham số 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

Ngoài ra, bạn có thể sử dụng (ComboBox5.SelectedItem IsNot Nothing) AndAlso...


Visual Basic Forms

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

Đây là một cách khá phổ biến để có được một NRE. Trong C #, tùy thuộc vào cách nó được mã hóa, IDE sẽ báo cáo rằng Controls không tồn tại trong ngữ cảnh hiện tại hoặc "không thể tham chiếu thành viên không tĩnh". Vì vậy, ở một mức độ nào đó, đây là tình huống chỉ có VB. Nó cũng phức tạp vì nó có thể dẫn đến một thác thất bại.

Các mảng và các bộ sưu tập không thể được khởi tạo theo cách này. Mã khởi tạo này sẽ chạy trước constructor tạo ra Form hoặc là Controls. Kết quả là:

  • Danh sách và Bộ sưu tập sẽ đơn giản là trống
  • Mảng sẽ chứa năm phần tử của Không có gì
  • Các somevar nhiệm vụ sẽ dẫn đến NRE ngay lập tức vì Không có gì không có .Text bất động sản

Việc tham chiếu các phần tử mảng sau sẽ dẫn đến một NRE. Nếu bạn làm điều này trong Form_Load, do một lỗi kỳ lạ, IDE có thể không báo cáo ngoại lệ khi nó xảy ra. Ngoại lệ sẽ bật lên một lát sau khi mã của bạn cố gắng sử dụng mảng. "Ngoại lệ im lặng" này là chi tiết trong bài đăng này. Vì mục đích của chúng tôi, điều quan trọng là khi có điều gì đó thảm khốc xảy ra khi tạo biểu mẫu (Sub New hoặc là Form Load sự kiện), ngoại lệ có thể không được báo cáo, mã sẽ thoát khỏi quy trình và chỉ hiển thị biểu mẫu.

Vì không có mã nào khác trong Sub New hoặc là Form Load sự kiện sẽ chạy sau NRE, rất nhiều thứ khác có thể không được khởi tạo.

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

chú thích điều này áp dụng cho bất kỳ và tất cả các kiểm soát và tham chiếu thành phần làm cho các tài liệu này bất hợp pháp ở đâu:

Public Class Form1

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

Biện pháp khắc phục một phần

Điều tò mò là VB không đưa ra cảnh báo, nhưng biện pháp khắc phục là khai các thùng chứa ở cấp biểu mẫu, nhưng khởi tạo chúng trong trình xử lý sự kiện tải biểu mẫu khi các điều khiển làm hiện hữu. Điều này có thể được thực hiện trong Sub New miễn là mã của bạn là sau InitializeComponent gọi điện:

' 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

Mã mảng có thể không được ra khỏi rừng. Bất kỳ điều khiển nào nằm trong điều khiển vùng chứa (như GroupBox hoặc là Panel) sẽ không được tìm thấy trong Me.Controls; họ sẽ có trong bộ sưu tập Điều khiển của Bảng điều khiển đó hoặc GroupBox. Cũng không một điều khiển sẽ được trả về khi tên điều khiển bị viết sai chính tả ("TeStBox2"). Trong trường hợp này, Nothing một lần nữa sẽ được lưu trữ trong các phần tử mảng đó và một NRE sẽ xuất hiện khi bạn cố gắng tham chiếu nó.

Những điều này sẽ dễ dàng tìm thấy ngay bây giờ mà bạn biết những gì bạn đang tìm kiếm: VS shows you the error of your ways

"Button2" nằm trên một Panel

Biện pháp khắc phục

Thay vì tham chiếu gián tiếp theo tên bằng cách sử dụng biểu mẫu Controlsbộ sưu tập, sử dụng tham chiếu điều khiển:

' 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...})

Chức năng không trả lại gì

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

Đây là trường hợp IDE sẽ cảnh báo bạn rằng 'không phải tất cả các đường dẫn đều trả về một giá trị và NullReferenceException có thể dẫn đến'. Bạn có thể chặn cảnh báo bằng cách thay thế Exit Function với Return Nothing, nhưng điều đó không giải quyết được vấn đề. Bất cứ điều gì cố gắng sử dụng sự trở lại khi someCondition = False sẽ dẫn đến một NRE:

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

Biện pháp khắc phục

Thay thế Exit Function trong hàm với Return bList. Trả lại một trống  List không giống như trở về Nothing. Nếu có cơ hội một đối tượng trả về có thể Nothing, kiểm tra trước khi sử dụng nó:

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

Thực hiện thử / bắt kém

Try / Catch được triển khai kém có thể ẩn nơi xảy ra sự cố và dẫn đến sự cố mới:

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

Đây là trường hợp của một đối tượng không được tạo như mong đợi, nhưng cũng thể hiện tính hữu ích của bộ đếm trống Catch.

Có một dấu phẩy phụ trong SQL (sau 'mailaddress') mà kết quả là một ngoại lệ tại .ExecuteReader. Sau Catch Không lam gi cả, Finally cố gắng dọn dẹp, nhưng vì bạn không thể Close một null DataReader đối tượng, một thương hiệu mới NullReferenceException các kết quả.

Trống rỗng Catch khối là sân chơi của ma quỷ. OP này bị bối rối vì sao anh ta nhận được NRE trong Finally khối. Trong các tình huống khác, trống Catch có thể dẫn đến cái gì đó khác xa hơn nữa hạ lưu đi haywire và làm cho bạn dành thời gian nhìn vào những điều sai trái ở chỗ sai cho vấn đề. ("Ngoại lệ im lặng" được mô tả ở trên cung cấp cùng một giá trị giải trí.)

Biện pháp khắc phục

Không sử dụng các khối try / catch trống - hãy để mã bị sập để bạn có thể a) xác định nguyên nhân b) xác định vị trí và c) áp dụng biện pháp khắc phục phù hợp. Các khối try / catch không nhằm che giấu ngoại lệ của người duy nhất đủ điều kiện để khắc phục chúng - nhà phát triển.


DBNull không giống như Không có gì

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

Các IsDBNull chức năng được sử dụng để kiểm tra nếu giá trị bằng System.DBNull: Từ MSDN:

Giá trị System.DBNull chỉ ra rằng đối tượng đại diện cho dữ liệu bị thiếu hoặc không tồn tại. DBNull không giống như Nothing, chỉ ra rằng một biến chưa được khởi tạo.

Biện pháp khắc phục

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

Như trước đây, bạn có thể kiểm tra Không có gì, sau đó cho một giá trị cụ thể:

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

Ví dụ 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 trả về mục đầu tiên hoặc giá trị mặc định, là Nothing cho các loại tham chiếu và không bao giờ DBNull:

If getFoo IsNot Nothing Then...

Kiểm soát

Dim chk As CheckBox

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

Nếu một CheckBox với chkName không thể tìm thấy (hoặc tồn tại trong một GroupBox), sau đó chk sẽ không có gì và cố gắng tham chiếu bất kỳ tài sản nào sẽ dẫn đến một ngoại lệ.

Biện pháp khắc phục

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

DataGridView

Các DGV có một vài quirks thấy định kỳ:

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"

Nếu dgvBooks có AutoGenerateColumns = True, nó sẽ tạo ra các cột, nhưng nó không đặt tên cho chúng, vì vậy mã trên không thành công khi nó tham chiếu chúng theo tên.

Biện pháp khắc phục

Đặt tên cho các cột theo cách thủ công hoặc tham chiếu theo chỉ mục:

dgvBooks.Columns(0).Visible = True

Ví dụ 2 - Hãy coi chừng 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

Khi bạn DataGridView có AllowUserToAddRows như True (mặc định), Cells trong hàng trống / mới ở cuối tất cả sẽ chứa Nothing. Hầu hết các nỗ lực để sử dụng nội dung (ví dụ: ToString) sẽ dẫn đến NRE.

Biện pháp khắc phục

Sử dụng một For/Each lặp lại và kiểm tra IsNewRow tài sản để xác định nếu nó là hàng cuối cùng. Điều này làm việc cho dù AllowUserToAddRows là đúng hay không:

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

Nếu bạn sử dụng For n lặp lại, sửa đổi số lượng hàng hoặc sử dụng Exit For khi nào IsNewRow là đúng.


My.Settings (StringCollection)

Trong một số trường hợp nhất định, cố gắng sử dụng một mục từ My.Settings mà là một StringCollection có thể dẫn đến một NullReference lần đầu tiên bạn sử dụng nó. Giải pháp là như nhau, nhưng không rõ ràng. Xem xét:

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

Vì VB đang quản lý Cài đặt cho bạn, nên việc khởi tạo bộ sưu tập là hợp lý. Nó sẽ, nhưng chỉ khi bạn đã thêm một mục nhập ban đầu vào bộ sưu tập (trong trình chỉnh sửa Cài đặt). Vì bộ sưu tập (rõ ràng) được khởi tạo khi một mục được thêm vào, nó vẫn còn Nothing khi không có mục nào trong trình chỉnh sửa Cài đặt để thêm.

Biện pháp khắc phục

Khởi tạo bộ sưu tập cài đặt trong biểu mẫu Load xử lý sự kiện, nếu / khi cần:

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

Thông thường, Settings bộ sưu tập sẽ chỉ cần được khởi tạo lần đầu tiên ứng dụng chạy. Một biện pháp khắc phục thay thế là thêm giá trị ban đầu vào bộ sưu tập của bạn bằng Dự án -> Cài đặt | FooBars, lưu dự án, sau đó xóa giá trị giả.


Những điểm chính

Bạn có thể quên New nhà điều hành.

hoặc là

Một cái gì đó bạn giả định sẽ thực hiện hoàn hảo để trả lại một đối tượng khởi tạo cho mã của bạn, không.

Đừng bỏ qua cảnh báo trình biên dịch (bao giờ) và sử dụng Option Strict On (luôn luôn).


Ngoại lệ NullReference MSDN


273





Một tình huống khác là khi bạn đưa một đối tượng null vào một loại giá trị. Ví dụ: mã bên dưới:

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

Nó sẽ ném một NullReferenceException trên dàn diễn viên. Nó có vẻ khá rõ ràng trong ví dụ trên, nhưng điều này có thể xảy ra trong các kịch bản phức tạp hơn "trễ ràng buộc" trong đó đối tượng null đã được trả về từ một số mã bạn không sở hữu, và dàn diễn viên được ví dụ tạo ra bởi một số hệ thống tự động.

Một ví dụ về điều này là đoạn liên kết ASP.NET đơn giản với điều khiển Lịch:

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

Đây, SelectedDate thực tế là thuộc tính - trong tổng số DateTime loại - của Calendar Web Control type, và ràng buộc hoàn toàn có thể trả lại một cái gì đó null. Trình tạo ASP.NET ngầm định sẽ tạo một đoạn mã sẽ tương đương với mã trình diễn ở trên. Và điều này sẽ nâng cao NullReferenceException đó là khá khó khăn để phát hiện, bởi vì nó nằm trong ASP.NET tạo ra mã mà biên dịch tốt ...


217



Cú bắt tuyệt vời. Một cách để tránh: DateTime x = (DateTime) o as DateTime? ?? defaultValue; - Serge Shultz


Nó có nghĩa là biến trong câu hỏi được chỉ vào không có gì. Tôi có thể tạo ra điều này như sau:

SqlConnection connection = null;
connection.Open();

Điều đó sẽ ném lỗi bởi vì trong khi tôi đã khai báo biến "connection", nó không được chỉ ra bất cứ điều gì. Khi tôi cố gắng gọi cho thành viên"Open", không có tham chiếu cho nó để giải quyết, và nó sẽ ném lỗi.

Để tránh lỗi này:

  1. Luôn khởi tạo các đối tượng của bạn trước khi bạn cố gắng làm bất cứ điều gì với chúng.
  2. Nếu bạn không chắc liệu đối tượng có rỗng hay không, hãy kiểm tra nó bằng object == null.

Công cụ Resharper của JetBrains sẽ xác định mọi vị trí trong mã của bạn có khả năng xảy ra lỗi tham chiếu null, cho phép bạn đặt trong một kiểm tra rỗng. Lỗi này là nguồn số một của lỗi, IMHO.


146



Công cụ Resharper của JetBrains sẽ xác định mọi vị trí trong mã của bạn có khả năng xảy ra lỗi tham chiếu null. Điều này là không chính xác. Tôi có một giải pháp mà không có phát hiện đó, nhưng mã đôi khi dẫn đến ngoại lệ. Tôi nghi ngờ nó đôi khi không thể phát hiện - ít nhất là họ - khi đa luồng có liên quan, nhưng tôi không thể bình luận thêm vì tôi chưa xác định được vị trí của lỗi của tôi. - j riv
Nhưng làm thế nào để giải quyết nó khi NullReferenceException xuất hiện trong Httpignext.Current.Responce.Clear (). Nó không được giải quyết bằng bất kỳ giải pháp nào ở trên. bởi vì trong khi tạo đối tượng đối tượng của HttpContext thì một lỗi xuất hiện "Độ phân giải quá tải không thành công vì không thể truy cập 'Mới' chấp nhận Số đối số này. - Sunny Sandeep


Điều đó có nghĩa là mã của bạn đã sử dụng một biến tham chiếu đối tượng đã được đặt thành null (tức là nó không tham chiếu một cá thể đối tượng thực tế).

Để ngăn chặn lỗi, các đối tượng có thể là null phải được kiểm tra trước khi sử dụng.

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
}

135





Lưu ý rằng bất kể kịch bản, nguyên nhân luôn giống nhau trong .NET:

Bạn đang cố gắng sử dụng biến tham chiếu có giá trị là Nothing/null. Khi giá trị là Nothing/null cho biến tham chiếu, điều đó có nghĩa là nó không thực sự giữ một tham chiếu đến một cá thể của bất kỳ đối tượng nào tồn tại trên heap.

Bạn không bao giờ được gán một cái gì đó cho biến, không bao giờ tạo ra một thể hiện của giá trị được gán cho biến, hoặc bạn đặt biến bằng Nothing/null theo cách thủ công hoặc bạn đã gọi một hàm đặt biến thành Nothing/null cho bạn.


90





Một ví dụ về ngoại lệ này được ném là: Khi bạn đang cố gắng kiểm tra một cái gì đó, đó là null.

Ví dụ:

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
} 

Thời gian chạy .NET sẽ ném ra một NullReferenceException khi bạn cố gắng thực hiện một hành động trên một cái gì đó chưa được khởi tạo tức là mã ở trên.

So với một ArgumentNullException mà thường được ném như một biện pháp phòng thủ nếu một phương pháp hy vọng rằng những gì đang được truyền cho nó không phải là null.

Có thêm thông tin C # NullReferenceException và tham số Null.


76