如何將C# 7類庫升級到C# 8?使用可空引用類型

這篇文章將介紹將C# 7類庫升級到C# 8(支持可空引用類型)的一個案例。本案例中使用的項目Tortuga Anchor由一組MVVM風格的基類、反射代碼和各種實用程序函數組成。之所以選擇這個項目,是因為它很小,并且同時包含了慣用和不常用的C#模式。

關鍵要點

  • 為每個項目啟用可空引用類型。
  • 使用泛型時,可能需要禁用可空引用類型。
  • 可以通過在本地變量中緩存屬性來修復警告。
  • 公開方法仍然需要進行Null參數檢查。
  • .NET Framework和.NET Core的反序列化方式是不一樣的。

這篇文章將介紹將C# 7類庫升級到C# 8(支持可空引用類型)的一個案例。本案例中使用的項目Tortuga Anchor由一組MVVM風格的基類、反射代碼和各種實用程序函數組成。之所以選擇這個項目,是因為它很小,并且同時包含了慣用和不常用的C#模式。

項目設置

目前,可空引用類型僅適用于.NET Standard和.NET Core項目。在Visual Studio 2019發布時,應該也支持.NET Framework。

在項目文件中,添加或修改以下配置:

\u0026lt;/PropertyGroup\u0026gt;? ? \u0026lt;LangVersion\u0026gt;8.0\u0026lt;/LangVersion\u0026gt;? ? \u0026lt;NullableContextOptions\u0026gt;enable\u0026lt;/NullableContextOptions\u0026gt;\u0026lt;/PropertyGroup\u0026gt;

在保存文件后,應該會看到可空性錯誤。如果沒有看到,請嘗試構建項目。

指示一個類型可以為空

在接口方法GetPreviousValue中,返回類型可以為空。為了顯式地說明這一點,可以在object后面跟上可空類型修飾符(?)。

object? GetPreviousValue(string propertyName);

使用這個類型修飾符注解變量、參數和返回類型,就可以解決項目中的很多編譯器錯誤。

延遲加載屬性

如果一個屬性的求值成本非常高,可以使用延遲加載模式。在使用這個模式時,如果私有字段為空,表示尚未生成字段的值。

C# 8可以很好地處理這種情況。在不改變代碼的情況下,它能夠正確地分析代碼,以確定getter的結果將始終非空,盡管返回的變量可以為空。

string? m_CSharpFullName;public string CSharpFullName{? ? get? ? {? ? ? ? if (m_CSharpFullName == null)? ? ? ? {? ? ? ? ? ? var result = new StringBuilder(m_TypeInfo.ToString().Length);? ? ? ? ? ? BuildCSharpFullName(m_TypeInfo.AsType(), null, result);? ? ? ? ? ? m_CSharpFullName = result.ToString();? ? ? ? }? ? ? ? return m_CSharpFullName;? ? }}

需要注意的是,這里存在潛在的競態條件。理論上,另一個線程可以將m_CSharpFullName的值設置回null,而編譯器無法檢測到。因此,在處理多線程代碼時要特別小心。

一個變量的可空性由另一個變量決定

在下一個代碼示例中,當且僅當m_ItemPropertyChanged不為空時,m_ListeningToItemEvents才為true。編譯器無法知道這個規則。如果是這種情況,你可以將(!)附加到變量(在本例中為m_ItemPropertyChanged)后面,表示它在這個時間點不會為空。

if (m_ListeningToItemEvents){? ? if (item is INotifyPropertyChangedWeak)? ? ? ? ((INotifyPropertyChangedWeak)item).AddHandler(m_ItemPropertyChanged!);? ? else if (item is INotifyPropertyChanged)? ? ? ? ((INotifyPropertyChanged)item).PropertyChanged += OnItemPropertyChanged;}

使用顯式強制轉換糾正誤報

在下一個示例中,編譯器錯誤地報告了m_Base的可空性。Values與IEnumerable的值不兼容。要移除這個警告,我添加了顯式強制轉換。

readonly Dictionary\u0026lt;ValueTuple\u0026lt;TKey1, TKey2\u0026gt;, TValue\u0026gt; m_Base;IEnumerable\u0026lt;TValue\u0026gt; IReadOnlyDictionary\u0026lt;ValueTuple\u0026lt;TKey1, TKey2\u0026gt;, TValue\u0026gt;.Values{? ? get { return (IEnumerable\u0026lt;TValue\u0026gt;)m_Base.Values; }}

請注意編譯器將該行標記為具有冗余強制轉換。這是正常的編譯器消息,而不是警告,但希望在發布時能夠得到更正。

使用臨時變量或條件強制轉換糾正誤報

在下一個示例中,編譯器指出CancelEdit所在行存在一個錯誤。雖然前面的if語句證明item.Value不為空,但編譯器不相信下次讀取item.Value時它仍然是不為空。

foreach (var item in m_CheckpointValues){? ? if (item.Value is IEditableObject)? ? ? ? ((IEditableObject)item.Value).CancelEdit();}

我們可以將item.Value保存在一個臨時變量中。

foreach (var item in m_CheckpointValues){? ? object? value = item.Value;? ? if (value is IEditableObject)? ? ? ? ((IEditableObject)value).CancelEdit();}

對于這種情況,我們可以通過使用條件轉換(as操作符)后面跟上一個條件方法調用(?.操作符)進一步簡化它。

foreach (var item in m_CheckpointValues){? ? (item.Value as IEditableObject)?.CancelEdit();}

泛型和可空類型

如果你經常使用泛型,可能會遇到一個有問題的可空類型。看一下這個delegate:

public delegate void ValueChanged\u0026lt;in T\u0026gt;(T oldValue, T newValue);

這個delegate的預期設計是oldValue和newValue都可以為空。所以,你會認為加幾個問號就可以解決問題。但是,這樣做會返回下面這樣的錯誤消息:

Error CS8627 可空類型參數必須是值類型或非可空的引用類型。可以考慮添加“class”、“struct”或類型約束。

如果你需要同時支持值類型和引用類型,那么這個問題就沒那么容易解決。由于你無法在類型約束中表達“or”,你需要一個用于類的delegate和一個用于結構體的delegate。

public delegate void ValueChanged\u0026lt;in T\u0026gt;(T? oldValue, T? newValue) where T : class;public delegate void ValueChanged\u0026lt;T\u0026gt;(T? oldValue, T? newValue) where T : struct;

但是,這樣不起作用,因為兩個delegate具有相同的名稱。你可以給它們起不一樣的名稱,但你必須復制使用它們的代碼。

所幸的是,C#有一個轉義值。你可以使用#nullable指令恢復成C #7的語義,這樣就可以達到預期的效果。

#nullable disablepublic delegate void ValueChanged\u0026lt;in T\u0026gt;(T oldValue, T newValue);#nullable enable

這種方法并非沒有缺陷。禁用可空引用可能是個好東西,但也可能什么都不是。你無法用它來讓oldValue變成可空或讓newValue變成不可空。

構造函數、反序列化器和初始化方法

對于下一個示例,你必須知道序列化器的一些技巧。有一個鮮為人知的函數用來繞過一個叫作FormatterServices.GetUninitializedObject的類構造函數。一些序列化器(如DataContractSerializer)使用它來提高性能。

如果你總是要運行構造函數中的邏輯,應該怎么辦?這個時候需要用到OnDeserializing屬性。這個屬性充當在GetUninitializedObject之后調用的代理構造函數。

為了減少冗余和出錯的可能性,開發人員通常會使用常見的初始化方法,如下面的代碼所示。

protected AbstractModelBase(){? ? Initialize();}?[OnDeserializing]void _ModelBase_OnDeserializing(StreamingContext context){? ? Initialize();}void Initialize(){? ? m_PropertyChangedEventManager = new PropertyChangedEventManager(this);? ? m_Errors = new ErrorsDictionary();}

這對null檢查器來說是個問題。由于構造函數中沒有顯式地設置上述兩個變量,因此它會把它們標記為未初始化。這意味著需要進行一些復制粘貼工作來移除這個錯誤。

還有一個風險,那就是忘記包含OnDeserializing方法。由于null檢查器不理解OnDeserializing方法,因此如果出現意外空值就無法提醒你。

大多數開發人員發現這種行為令人困惑。因此,在.NET Core中,DataContractSerializer將調用構造函數。但這意味著如果你的目標是.NET Standard,則需要使用.NET Framework和.NET Core測試反序列化代碼,以理解不同的行為。

可空參數和CallerMemberName

這個庫大量使用了CallerMemberName模式。根據它使用的屬性命名,基本思想是在方法的末尾添加一個可選參數。編譯器將看到CallerMemberName,并隱式地為該參數提供一個值。

public override bool IsDefined([CallerMemberName] string propertyName = null)

從理論上講,propertyNameparameter可以顯式設置為null,但人們普遍認為不應該這樣做,因為這樣可能會發生意外的錯誤。

將這行代碼轉換為C# 8時,可能會想要將參數標記為可空。這樣具有誤導性,因為這個方法實際上并不是為處理空值而設計的。相反,你應該用空字符串替換null。

public override bool IsDefined([CallerMemberName] string propertyName = \u0026quot;\u0026quot;)

還需要空參數檢查嗎?

如果要構建公共庫(即NuGet),那么是的,所有公開方法仍然需要檢查空參數。使用庫的應用程序可能不一定會使用可空引用類型。事實上,他們甚至可能根本不使用C# 8。

如果你的所有應用程序代碼都使用了可空引用類型,那么答案仍然是“可能是”。雖然從理論上講,你不會看到任何意外的空值,但由于動態代碼、反射或誤用(!)操作符,它們仍然可能會出現。

結論

在一個只有不到60個類文件的項目中,其中24個類文件需要更改。但沒有一個是特別重要的,整個過程花了不到一個小時。總之,這是一個無痛的過程,大多數事情都像預期的那樣。我希望大多數項目都能從這個特性中獲益,并且在C# 8發布后就應該使用這個特性。

關于作者

\"\"

Jonathan Allen在90年代后期開始為一家醫療診所做MIS項目,逐步將Access和Excel應用到企業解決方案中。在花了五年時間為金融行業編寫自動化交易系統之后,他成為了多個項目的顧問,其中包括機器人倉庫的UI、癌癥研究軟件的中間層,以及一家大型房地產保險公司對大數據的需求。在他的空閑時間,他喜歡學習和寫作與16世紀武術相關的東西。

英文原文:https://www.infoq.com/articles/csharp-nullable-reference-case-study

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/280018.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/280018.shtml
英文地址,請注明出處:http://en.pswp.cn/news/280018.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

android 設備名稱_如何更改您的Android TV的設備名稱

android 設備名稱Android TV is Google’s attempt at taking over the living room, and with some units being available for under $99, it’s not unheard of for users to have more than one box. The problem is, when multiple devices identify themselves identical…

AD-查找符合指定條件的用戶Get-User

以下服務器為Exchange 2010一、使用 Get-User 命令查找部門為IT的用戶Get-User -ResultSize Unlimited | ? { $_.Department -Eq "IT" } | ft Name,Department二、查找注釋為多行內容的指定用戶如下圖:注釋Notes信息為多行要使用 match 和 (?*) 來做匹配…

目標檢測算法之Fast R-CNN算法詳解

在介紹Fast R-CNN之前我們先介紹一下SPP Net 一、SPP Net SPP:Spatial Pyramid Pooling(空間金字塔池化) 眾所周知,CNN一般都含有卷積部分和全連接部分,其中,卷積層不需要固定尺寸的圖像,而全連…

RGB-D(深度圖像) 圖像深度

RGB-D(深度圖像) 深度圖像 普通的RGB三通道彩色圖像 Depth Map 在3D計算機圖形中,Depth Map(深度圖)是包含與視點的場景對象的表面的距離有關的信息的圖像或圖像通道。其中,Depth Map 類似于灰度圖像&…

WPF-21 基于MVVM員工管理-01

接下來我們通過兩節課程使用MVVM來開發一個簡單的Demo,首先我們創建一個項目名稱WPF-22-MVVM-Demo,目錄結構如下:我們在Models文件下創建Employee類并讓該類實現INotifyPropertyChanged接口,該類中定義編號、姓名和角色三個基本屬…

qt 蘋果應用程序_什么是蘋果的電視應用程序,您應該使用它嗎?

qt 蘋果應用程序Apple’s TV app, which recently appeared on iOS devices and Apple TV, is meant to help users discover and watch shows across an increasingly expanding lineup of television channels, as well as iTunes movies and shows, in one central app. App…

細說flush、ob_flush的區別

ob_flush/flush在手冊中的描述, 都是刷新輸出緩沖區, 并且還需要配套使用, 所以會導致很多人迷惑… 其實, 他們倆的操作對象不同, 有些情況下, flush根本不做什么事情.. ob_*系列函數, 是操作PHP本身的輸出緩沖區. 所以, ob_flush是刷新PHP自身的緩沖區. 而flush, 嚴格來講, 這…

關于jHipster框架在構建中的出現的error修復

jhipster The JDL object and the database type are both mandatory.這個錯誤應該是在構建基于jHipster的spring-cloud項目中經常遇到的,因為這個在這個過程中會讀取.yo-rc文件,之后生成相關的.json文件,再之后生成相關的.java文件&#xff…

protobuf編碼

proto2Protocol Buffers 是一種輕便高效的結構化數據存儲格式,可以用于結構化數據序列化,適合做數據存儲或 RPC 數據交換格式。可用于通訊協議、數據存儲等領域的語言無關、平臺無關、可擴展的序列化結構數據格式。 字段規則 required: 字段必須存在opti…

定制.NET 6.0的Middleware中間件

大家好,我是張飛洪,感謝您的閱讀,我會不定期和你分享學習心得,希望我的文章能成為你成長路上的墊腳石,讓我們一起精進。在本文中,我們將學習中間件,以及如何使用它進一步定制應用程序。我們將快…

Python-循環控制--個人課堂筆記

Python中的兩種循環方式(目前學到):for循環和while循環 for循環和while循環的區別: for循環一般用于控制循環的次數,while循環則是條件循環。 操作實例-猜數字小游戲(3次猜錯提示游戲結束)&…

刪除microsoft_如何從您的Microsoft帳戶中刪除設備

刪除microsoftWhen you sign into Windows 8 or 10 using your Microsoft account (and other Microsoft devices, like an Xbox), those devices become associated with your account. If you want to remove an old device you’ve gotten rid of, you’ll have to pay a vi…

線程的語法 (event,重要)

Python threading模塊 2種調用方式 直接調用 12345678910111213141516171819import threadingimport timedef sayhi(num): #定義每個線程要運行的函數print("running on number:%s" %num)time.sleep(3)if __name__ __main__:t1 threading.Thread(targetsayhi,args(…

求最大值和下標值

本題要求編寫程序&#xff0c;找出給定的n個數中的最大值及其對應的最小下標&#xff08;下標從0開始&#xff09;。 輸入格式: 輸入在第一行中給出一個正整數n&#xff08;1<n≤10&#xff09;。第二行輸入n個整數&#xff0c;用空格分開。 輸出格式: 在一行中輸出最大值及…

windows應用商店修復_如何修復Windows應用商店中的卡死下載

windows應用商店修復Though it’s had its share of flaky behavior since being introduced in Windows 8, the Windows Store has gotten more reliable over time. It still has the occasional problems, though. One of the more irritating issues is when an app update…

OpenWrt:Linux下生成banner

Linux下有三個小工具可以生成banner&#xff1a;1、banner使用#生成banner&#xff1b;2、figlet使用一些普通字符生成banner&#xff1b;3、toilet使用一些復雜的彩色特殊字符生成banner。使用apt-get安裝的時候需要輸入以下命令&#xff1a; $ sudo apt-get install sysvbann…

新冠病毒中招 | 第二天

今天跟大家分享我個人感染奧密克戎毒株第二天的經歷和感受。早上7點多自然醒來&#xff0c;已經沒有四肢乏力的感覺&#xff0c;但是身體的本能還是告訴我不愿意動彈。由于第一天躺著睡了一天&#xff0c;確實是躺得腰酸背疼的。起床量了一下體溫36.4正常&#xff0c;決定今天不…

輸出到Excel

HSSFWorkbook oBook new HSSFWorkbook(); NPOI.SS.UserModel.ISheet oSheet oBook.CreateSheet(); #region 輸出到Excel MemoryStream ms new MemoryStream(); oBook.Write(ms);string sExportPath ""; using (SaveFileDialog saveFileDialog1 new SaveFileDial…

JavaScript 精粹 基礎 進階(5)數組

轉載請注明出處 原文連接 blog.huanghanlian.com/article/5b6… 數組是值的有序集合。每個值叫做元素&#xff0c;每個元素在數組中都有數字位置編號&#xff0c;也就是索引。JS中的數組是弱類型的&#xff0c;數組中可以含有不同類型的元素。數組元素甚至可以是對象或其它數組…

icloud 購買存儲空間_如何釋放iCloud存儲空間

icloud 購買存儲空間Apple offers 5 GB of free iCloud space to everyone, but you’ll run up against that storage limit sooner than you’d think. Device backups, photos, documents, iCloud email, and other bits of data all share that space. Apple為每個人提供5 …