自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

什么是空引用異常(NullReferenceException),我該怎么修復它?

開發(fā) 前端
C#有一個“不安全”模式,顧名思義,非常危險,因為不強制執(zhí)行提供內存安全性和類型安全性的正常安全機制。除非您深入了解內存工作原理,否則不應編寫不安全的代碼。

在C#中,空引用異常(NullReferenceException)是最常見的異常之一。它發(fā)生在你嘗試訪問或操作一個空對象時。換句話說,當你沒有為變量分配一個有效的對象引用,卻嘗試使用它時,就會拋出空引用異常。

引發(fā)的原因是什么

您正在嘗試使用空值(或 VB.NET 中的“Nothing”)。這意味著您將其設置為 null,或者根本沒有設置任何值。

與其他任何東西一樣,空值會傳遞。如果在方法“A”中為 null,則可能是方法“B”將 null 傳遞給了方法“A”。

空值可能有不同的含義:

  • 未初始化的對象變量,因此指向空值。在這種情況下,如果您訪問此類對象的成員,會導致 NullReferenceException。
  • 開發(fā)人員故意使用null來表示沒有可用的有意義的值。請注意,C#有變量的可空數(shù)據(jù)類型的概念(就像數(shù)據(jù)庫表可以有可空字段一樣)-您可以將null分配給它們,以指示其中沒有存儲值,例如int? a = null;(這是Nullable<int> a = null;的快捷方式;)其中問號表示允許在變量a中存儲null。您可以使用if(a.HasValue){...}或if(a==null){...}來檢查它??煽兆兞浚热邕@個例子,允許通過a.Value顯式訪問值,或者像正常情況下一樣通過a訪問。

請注意,通過a.Value訪問它如果a為空會引發(fā)InvalidOperationException而不是NullReferenceException-您應該事先進行檢查,即如果您有另一個非空變量int b;然后您應該進行賦值,如if(a.HasValue){b = a.Value;}或更短的if(a!=null){b = a;}。

這篇文章的其余部分會更詳細地介紹,并展示許多程序員經常犯的錯誤,這些錯誤可能導致空引用異常。

更具體地

運行時拋出 NullReferenceException 總是意味著同樣的事情:你試圖使用一個引用,但該引用沒有被初始化(或者曾經被初始化過,但現(xiàn)在已經不再被初始化)。

這意味著該引用是空的,你無法通過空引用訪問成員(比如方法)。這是最簡單的情況:

string foo = null;
foo.ToUpper();

這將在第二行拋出NullReferenceException,因為你不能在指向null的字符串引用上調用實例方法ToUpper()。

調試 Debugging

你如何找到 NullReferenceException 的源頭?除了查看異常本身會在發(fā)生異常的位置拋出之外,Visual Studio 調試的一般規(guī)則也適用:設置策略性的斷點并檢查你的變量,可以通過將鼠標懸停在它們的名稱上、打開(快速)監(jiān)視窗口或者使用諸如本地變量和自動變量等各種調試面板來進行。

如果你想找出引用是在哪里設置或未設置,右鍵單擊它的名稱并選擇“查找所有引用”。然后你可以在每個找到的位置設置斷點,并使用附加了調試器的程序運行。每當調試器在這樣的斷點上中斷時,你需要確定你是否期望引用是非空的,檢查變量,并驗證它在你期望的時候指向一個實例。

通過這種方式跟蹤程序流程,你可以找到實例不應為 null 的位置,以及為什么它沒有被正確設置。

案例 Examples

一些常見的異常拋出場景:

通用

ref1.ref2.ref3.member

如果ref1或ref2或ref3為空,那么你會得到一個NullReferenceException。如果你想解決這個問題,那么找出哪一個是空的,通過將表達式重寫為更簡單的等價形式來解決。

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

在HttpContext.Current.User.Identity.Name中,HttpContext.Current可能為null,或者User屬性可能為null,或者Identity屬性可能為null。

間接

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

如果你想避免子對象(People)的空引用,你可以在父對象(BooK)的構造函數(shù)中對其進行初始化。

嵌套對象初始化

同樣的規(guī)則也適用于嵌套對象初始化器:

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

轉換為:

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

雖然使用了新關鍵字,它只創(chuàng)建了 Book 的一個新實例,而不是 Person 的新實例,所以 Author 屬性仍然為空。

嵌套集合初始化器

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

嵌套集合初始化器的行為相同:

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 只會創(chuàng)建一個 Person 的實例,但 Books 集合仍然為空。集合初始化器語法不會為 p1.Books 創(chuàng)建一個集合,它只是將其轉換為 p1.Books.Add(...) 語句。

Array

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

Array Elements

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.

Jagged Arrays

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.

Collection/List/Dictionary

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.

Events (C#)

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

(注意:VB.NET 編譯器會在事件使用時插入空值檢查,因此在 VB.NET 中不需要檢查事件是否為 Nothing。)

糟糕的命名規(guī)范:

如果你將字段命名與局部變量不同,你可能會意識到你從未初始化該字段。

public class Form1
{
    private Customer customer;
    
    private void Form1_Load(object sender, EventArgs e) 
    {
        Customer customer = new Customer();
        customer.Name = "John";
    }
    
    private void Button_Click(object sender, EventArgs e)
    {
        MessageBox.Show(customer.Name);
    }
}

這個問題可以通過遵循在字段前加下劃線的命名約定來解決:

private Customer _customer;

ASP.NET Page Life cycle:

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

// 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 empty view models

如果在 ASP.NET MVC 視圖中引用 @Model 的屬性時發(fā)生異常,你需要明白 Model 是在你的操作方法中設置的,當你返回一個視圖時。當你從控制器返回一個空模型(或模型屬性)時,視圖在訪問它時會發(fā)生異常。

// 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 Creation Order and Events

WPF 控件在調用 InitializeComponent 時按照它們在可視樹中出現(xiàn)的順序創(chuàng)建。如果在 InitializeComponent 中引用了在后續(xù)創(chuàng)建階段的控件的早期創(chuàng)建控件中的事件處理程序等,就會引發(fā) NullReferenceException。

例如:

<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 之前創(chuàng)建。如果 comboBox1_SelectionChanged 嘗試引用 `label1,它可能尚未被創(chuàng)建。

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

改變 XAML 中聲明的順序(即,在 comboBox1 之前列出 label1,忽略設計哲學的問題)至少會解決這里的 NullReferenceException。

"as" 進行類型轉換

var myThing = someObject as Thing;

這種方法在類型轉換失敗時不會拋出 InvalidCastException,而是返回 null(當 someObject 本身為 null 時也是如此)。所以請注意這一點。

LINQFirstOrDefault()andSingleOrDefault()

普通的 First() 和 Single() 方法在沒有匹配項時會拋出異常。而帶有 "OrDefault" 后綴的版本會返回 null。所以請注意這一點。

foreach

當嘗試對空集合進行迭代時,foreach 會拋出異常。這通常是由返回空集合的方法意外返回 null 導致的。

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

更現(xiàn)實的例子是從 XML 文檔中選擇節(jié)點。如果未找到節(jié)點,會拋出異常,但初始調試顯示所有屬性都是有效的。

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

避免的方法

顯式檢查 null 并忽略 null 值

如果你期望引用有時會是 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,以便在異常發(fā)生之前更早地捕獲問題

當你在開發(fā)過程中知道一個方法可能會返回 null,但實際上不應該返回 null 時,你可以使用 Debug.Assert(),以便在出現(xiàn)這種情況時盡快中斷程序。

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

盡管這個檢查不會出現(xiàn)在發(fā)布版本中,但在運行時(在發(fā)布模式下)當 `book == null` 時,它會再次引發(fā) NullReferenceException。

使用 `GetValueOrDefault()` 方法來處理可空值類型,在其為 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.NET 中)來提供一個默認值

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

使用空值條件運算符:?.或?[x]用于數(shù)組(在C# 6和VB.NET 14中可用):

有時這也被稱為安全導航或Elvis(根據(jù)其形狀)運算符。如果運算符左側的表達式為空,那么右側將不會被計算,而是返回空值。這意味著像這樣的情況:

var title = person.Title.ToUpper();

如果person沒有title,這將拋出一個異常,因為它試圖在一個空值屬性上調用 ToUpper。

在C# 5及以下版本中,可以通過以下方式加以防范:

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

現(xiàn)在,title變量將為空,而不會拋出異常。C# 6引入了一個更簡潔的語法來實現(xiàn)這一點:

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

這將導致title變量為空,如果person.Title為空,就不會調用ToUpper。

當然,你仍然需要檢查title是否為空,或者使用空值條件運算符與空值合并運算符(??)一起使用,以提供一個默認值:

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

同樣,對于數(shù)組,你可以使用?[i]來實現(xiàn)如下功能:

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

這將實現(xiàn)以下功能:如果myIntArray為空,表達式將返回null,你可以安全地進行檢查。如果它包含一個數(shù)組,它將執(zhí)行與 elem = myIntArray[i]; 相同的操作,并返回第i個元素。

使用空值上下文 (C# 8中可用):

在C# 8中引入了空值上下文和可空引用類型,它們對變量進行靜態(tài)分析,并在值可能為空或已設置為null時提供編譯器警告??煽找妙愋驮试S明確允許類型為空。

可以使用csproj文件中的Nullable元素為項目設置可空注解上下文和可空警告上下文。此元素配置編譯器如何解釋類型的可空性以及生成哪些警告。有效的設置包括:

  • enable: 啟用可空注解上下文。啟用可空警告上下文。引用類型的變量,例如字符串,是非空的。所有可空性警告都已啟用。
  • disable: 禁用可空注解上下文。禁用可空警告上下文。引用類型的變量是不可知的,就像之前的C#版本一樣。所有可空性警告都已禁用。
  • safeonly: 啟用可空注解上下文。啟用安全可空警告上下文。引用類型的變量是非空的。所有安全的可空性警告都已啟用。
  • warnings: 禁用可空注解上下文。啟用可空警告上下文。引用類型的變量是不可知的。所有可空性警告都已啟用。
  • safeonlywarnings: 禁用可空注解上下文。啟用安全可空警告上下文。引用類型的變量是不可知的。所有安全的可空性警告都已啟用。

可空引用類型的表示與可空值類型相同:在變量類型后追加?。

迭代器中調試和修復空指針引用的特殊技巧

C#支持“迭代器塊”(在其他一些流行的語言中稱為“生成器”)。NullReferenceException在迭代器塊中可能特別難以調試,因為它具有延遲執(zhí)行的特性。

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

如果任何結果為空,那么MakeFrob就會拋出異?!,F(xiàn)在,你可能會認為正確的做法是這樣:

// 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的調用只是返回一個對象,當?shù)鷷r才運行迭代器塊。

通過編寫這樣的空值檢查,您可以避免NullReferenceException,但是將NullArgumentException移動到迭代點而不是調用點非常令人困惑。

正確的修復方法是:

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

這樣,創(chuàng)建一個私有的輔助方法,其中包含迭代器塊邏輯,以及一個公共的表面方法,用于進行空值檢查并返回迭代器?,F(xiàn)在,當調用GetFrobs時,空值檢查會立即發(fā)生,然后在迭代序列時執(zhí)行GetFrobsForReal。

如果你檢查LINQ to Objects的參考源代碼,你會發(fā)現(xiàn)這種技術被廣泛使用。這樣寫起來可能有點笨拙,但可以更輕松地調試空值錯誤。優(yōu)化你的代碼以方便調用者,而不是以方便作者為首要考慮。

關于不安全代碼中的空指針解引用說明

C#有一個“不安全”模式,顧名思義,非常危險,因為不強制執(zhí)行提供內存安全性和類型安全性的正常安全機制。除非您深入了解內存工作原理,否則不應編寫不安全的代碼。

在不安全模式下,您應該注意兩個重要事實:

解除引用空指針會產生與解除引用空引用相同的異常

在某些情況下,解除引用無效的非空指針也可能產生該異常

要理解其中的原因,了解.NET如何首先生成NullReferenceException會有所幫助。(這些細節(jié)適用于在Windows上運行的.NET;其他操作系統(tǒng)使用類似的機制。)

在Windows中,內存是虛擬化的;每個進程都獲得許多“頁面”的虛擬內存空間,這些頁面由操作系統(tǒng)跟蹤。每個內存頁面都有設置的標志,確定它如何使用:讀取、寫入、執(zhí)行等。最低頁面標記為“如果以任何方式使用,則產生錯誤”。

在C#中,空指針和空引用都被內部表示為數(shù)字零,因此任何嘗試將其解除引用為其對應的內存存儲都會導致操作系統(tǒng)產生錯誤。然后,.NET運行時檢測到此錯誤并將其轉換為NullReferenceException。

這就是為什么解除引用空指針和空引用都會產生相同異常的原因。

第二點呢?解除引用任何無效指針,該指針位于虛擬內存的最低頁面中,會導致相同的操作系統(tǒng)錯誤,從而導致相同的異常。

為什么這有意義呢?假設我們有一個包含兩個int和一個等于null的非托管指針的結構體。如果我們嘗試解除引用結構體中的第二個int,則CLR將不會嘗試訪問位置零處的存儲;它將訪問位置四處的存儲。但從邏輯上講,這是一個空解除引用,因為我們通過null到達了該地址。

如果您正在使用不安全的代碼并且遇到NullReferenceException,請注意有問題的指針不一定為空。它可以是最低頁面中的任何位置,并且將產生此異常。

責任編輯:姜華 來源: 今日頭條
相關推薦

2022-04-07 11:27:15

數(shù)字孿生VR系統(tǒng)AI

2018-01-08 14:18:14

代碼互聯(lián)網(wǎng)持續(xù)集成

2023-03-26 00:04:14

2011-02-23 10:45:51

IT人才

2014-08-13 11:20:10

創(chuàng)業(yè)者

2024-08-09 11:52:18

2023-11-07 08:00:00

Kubernetes

2018-03-22 14:47:13

容器開發(fā)人員筆記本

2017-10-25 09:50:51

Linux

2024-11-25 12:20:00

Hystrix微服務架構

2017-07-18 09:02:05

磁盤克隆軟件

2022-08-24 15:03:21

數(shù)據(jù)智能數(shù)據(jù)分析

2021-10-09 22:10:30

Windows 11Windows微軟

2020-08-10 15:48:01

Python輪子計算

2022-12-29 16:59:14

代碼審查編程

2010-03-21 16:27:22

UNIX系統(tǒng)x86服務器大型機

2017-08-10 08:38:31

互聯(lián)網(wǎng)+政務刷臉

2021-05-07 17:43:09

Windows 10Windows微軟

2022-11-28 11:47:47

物聯(lián)網(wǎng)IOT

2010-10-26 13:44:15

點贊
收藏

51CTO技術棧公眾號