簡體   English   中英

C#中可空類型的價值點是什么

[英]What Is The Point of Value on Nullable Types In C#

試圖更好地理解為什么這是語言功能:

我們有:

public static DateTime? Date { get; set; }


static void Main(string[] args)
{
    Date = new DateTime(2017, 5, 5);

    Console.WriteLine(Date.Value.Date);
    Console.Read();
}

為什么需要使用Value從可為空的類型中獲取值? 它不像在調用Date之前檢查是否為null,如果該值為null則將引發NullReference異常。 我明白為什么.HasValue可以工作,

但是不確定為什么每個空值類型都需要.Value嗎?

首先讓我們澄清這個問題。

在C#1.0中,我們有兩大類類型:值類型(永遠不為null)和引用類型(為可空)。 *

值類型和引用類型均支持成員訪問運算符 . ,它選擇與實例關聯的值。

對於引用類型,之間的關系. 運算符,並且接收方的可為空性是:如果接收方為空引用,則使用. 運算符產生異常。 由於C#1.0值類型首先不可為空,因此無需指定當您時會發生什么. 空值類型; 他們不存在。

在C#2.0中,添加了可為空的值類型。 就其在內存中的表示而言,可空值類型並不是什么神奇的東西。 它只是一個帶有值實例的結構,並且有一個布爾值說明它是否為null。

盡管有一些可編譯的魔術( ** ),因為可空值類型伴隨着語義的提升 通過提升,我們的意思是對可空值類型進行的操作的語義是“如果值不為空,則對值進行操作並將結果轉換為可為空的類型;否則結果為空”。 ***

也就是說,如果我們有

int? x = 2;
int? y = 3;
int? z = null;
int? r = x + y; // nullable 5
int? s = y + z; // null

在后台,編譯器正在做各種魔術以有效地實現提升的算法。 如果這個主題引起您的興趣,請參閱我冗長的博客系列,介紹如何編寫優化器

但是, . 操作員解除。 它可能是! 至少有兩種可行的設計是有意義的:

  1. nullable.Whatever()可以像可為空的引用類型一樣執行:如果接收者為null,則拋出異常,或者
  2. 它的行為可能類似於可為空的算術:如果nullable為null,則將忽略對Whatever()的調用,並且結果將為返回了Whatever()任何類型的null。

所以問題是:

為什么要求.Value. 當有一個明智的設計. 運算符只是工作並提取基礎類型的成員?

好。

注意我剛才在那做什么。 有兩種可能性都非常有意義,並且與該語言的既定且易於理解的方面一致,並且彼此矛盾 語言設計者發現自己在這進退兩難的所有時間 我們現在處在一種情況是否完全不清楚的情況下. 在可空值類型上的行為應類似於. 在引用類型上,或者是否. 在可為null的int上的行為應類似於+ 兩者都是合理的。 無論選擇哪一個,都會有人認為這是錯誤的。

語言設計團隊考慮了其他選擇,例如使其明確。 例如,“貓王” ?. 運算符,明確是提升為可空的成員訪問權限。 C#2.0曾考慮使用此方法,但此方法被拒絕,然后最終添加到C#6.0中。 還考慮了其​​他一些句法解決方案,但由於歷史原因而被全部拒絕。

我們已經看到我們有一個潛在的設計雷區. 關於值類型,但是等等,情況變得更糟。

現在讓我們考慮的另一方面. 當應用於值類型時:如果值類型是可怕的可變值類型 ,並且成員是field ,則xy變量(如果x是變量),否則是值。 也就是說,如果x是變量,則xy = 123是合法的。 但是,如果x不是變量,則C#編譯器將不允許該賦值,因為該賦值將被復制到value的副本上

這與可為空的值類型有何關系? 如果我們有一個可為空的可變值,則鍵入X? 那是什么

x.y = 123

做? 請記住, x確實是不可變類型Nullable<X>的實例,因此,如果這意味着x.Value.y = 123那么我們正在對Value屬性返回的Value的副本進行變異 ,這似乎非常非常錯誤。

那么我們該怎么辦? 可空值類型本身應該可變嗎? 這種變異將如何起作用? 它是復制進復制出語義嗎? 這將意味着ref xy將是非法的,因為ref需要變量而不是屬性。

這將是一個巨大的怪胎

在C#2.0中,設計團隊試圖將泛型添加到該語言中。 如果您曾經嘗試將泛型添加到現有的類型系統中,那么您會知道它的工作量。 如果您還沒有,那么,這是很多工作。 我認為設計團隊可以決定是否在所有這些問題上做出決定. 對於可空值類型沒有特殊含義。 “如果您想要值,那么您就可以調用.Value ”,其好處是不需要設計團隊的任何特殊工作! 同樣,“如果使用可變的可為空的值類型很麻煩,那么也許停止這樣做”對於設計人員來說是低成本的。


如果我們生活在一個完美的世界中,那么在C#1.0中我們將擁有兩種正交類型:引用類型與值類型,以及可空類型與不可空類型。 十年半之后,我們得到的是C#1.0中的可為空的引用類型和不可為空的值類型,C#2.0中的可為空值類型以及C#8.0中的kinda-sorta非可為空的引用類型。

在完美的世界里,我們會整理出所有的操作語義,起重語義,變量語義等, 一下子就使它們保持一致。

但是,嘿,我們沒有生活在這個完美的世界中。 我們生活在一個完美的世界是美好的敵人的世界,您必須說.Value. 代替. 在C#2.0到5.0,以及?. 在C#6.0中。


*我故意忽略了指針類型,這些指針類型可以為空,並且具有值類型的某些特性和引用類型的某些特性,並且它們具有用於取消引用和成員訪問的自己的特殊運算符。

**諸如此類的東西也有魔術:可空值類型不滿足值類型約束,可空值類型框為空引用或裝箱的基礎類型,以及許多其他小的特殊行為。 但是內存布局並不是什么神奇的事情。 這只是布爾值。

***函數式程序員當然會知道這是對monad的綁定操作。

這是由於如何實現可空類型。

Nullable<T>語法僅轉換為Nullable<T> ,這是您可以很好地編寫自己的結構(除了…?語法是該類型的語言功能之外)。

Nullable<T>的.NET Core實現是開源的, 其代碼有助於對此進行解釋。

Nullable<T>僅具有布爾類型的字段和基礎類型的value字段,並且在訪問.Value時僅引發異常:

public readonly struct Nullable<T> where T : struct
{
    private readonly bool hasValue; // Do not rename (binary serialization)
    internal readonly T value; // Do not rename (binary serialization)

    …

    public T Value
    {
        get
        {
            if (!hasValue)
            {
                ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_NoValue);
            }
            return value;
        }
    }
…

當執行類似DateTime aDateTime = (DateTime)nullableDateTime /賦值時,則僅調用在同一類上定義的運算符,該運算符的工作方式與在自定義類型上定義的運算符完全相同。 該運算符僅調用.Value因此.Value隱藏對屬性的訪問:

    public static explicit operator T(Nullable<T> value)
    {
        return value.Value;
    }

還有一個用於逆分配的運算符,所以DateTime? nullableNow = DateTime.Now DateTime? nullableNow = DateTime.Now將調用:

    public static implicit operator Nullable<T>(T value)
    {
        return new Nullable<T>(value);
    }

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM