簡體   English   中英

為什么Nullable <T> 是一個結構?

[英]Why Nullable<T> is a struct?

我想知道為什么Nullable<T>是一個值類型,如果它被設計為模仿引用類型的行為? 我理解GC壓力之類的東西,但我不相信 - 如果我們想讓int表現得像參考,我們可能會對所有具有真實參考類型的后果感到滿意。 我看不出為什么Nullable<T>不僅僅是T struct的盒裝版本。

作為價值類型:

  1. 它仍然需要裝箱和取消裝箱,而且更多,裝箱必須與“普通”結構有點不同(對待像null值一樣的null值可null
  2. 檢查null時需要區別對待 (僅在Equals完成,沒有真正的問題)
  3. 它是可變的,打破了結構應該是不可變的規則 (好吧,它在邏輯上是不可變的)
  4. 它需要有特殊限制來禁止Nullable<Nullable<T>>等遞歸

是不是使Nullable<T>成為引用類型解決問題?

改寫和更新:

我已經修改了我的理由列表,但我的一般問題仍然是開放的:

引用類型Nullable<T>將如何比當前值類型實現更糟糕? 這只是GC壓力和“小的,不可改變的”規則嗎? 它對我來說仍然很奇怪......

其原因是,它不是設計來像一個引用類型。 它的設計就像一個值類型,除了一個特定的類型。 讓我們看一下值類型和引用類型的不同之處。

值和引用類型之間的主要區別在於值類型是自包含的(包含實際值的變量),而引用類型是指另一個值。

其他一些差異也是由此引起的。 我們可以直接別名引用類型(具有好的和壞的效果)的事實來自於此。 平等意味着什么呢?

值類型具有基於所包含的值的相等概念,可以選擇性地重新定義(對重新定義的發生方式存在邏輯限制*)。 引用類型具有對於值類型無意義的標識概念(因為它們不能直接別名,因此兩個這樣的值不能相同),這些值無法重新定義,這也是其相等概念的默認值。 默認情況下, ==在涉及值類型†時處理這種基於值的相等性,但在引用類型時具有標識。 此外,即使參考類型被賦予基於值的相等概念,並且它用於==它也永遠不會失去與另一個身份參考進行比較的能力。

這樣做的另一個區別是引用類型可以為null - 引用另一個值的值允許一個不引用任何值的值,這是一個空引用。

此外,保持值類型較小的一些優點與此相關,因為基於值,它們在傳遞給函數時按值復制。

其他一些差異是暗示的,但不是由此引起的。 使價值類型不可變的通常是一個好主意是隱含的但不是由核心差異所引起的,因為雖然在不考慮實施問題的情況下可以找到優勢,但使用引用類型(實際上與安全性有關)也有一些優勢。別名更直接地應用於引用類型)以及可能違反此指南的原因 - 因此它不是一個硬性規則(使用嵌套值類型,所涉及的風險大大減少,以至於我在使嵌套值類型變為可變時幾乎沒有疑慮,即使我的風格很大程度上傾向於使偶數引用類型在實際中不可變。

值類型和引用類型之間的一些進一步差異可以說是實現細節。 局部變量中的值類型具有存儲在堆棧中的值已被認為是實現細節; 如果你的實現有一個堆棧,可能是一個非常明顯的一個,在某些情況下肯定是一個重要的,但不是定義的核心。 它也經常被誇大(對於一個開始,局部變量中的引用類型也在棧中具有引用本身,而另一個有很多時候值類型值存儲在堆中)。

值類型的一些進一步優點是與此相關。


現在, Nullable<T>是一種類型,其行為類似於上面描述的所有方式的值類型,除了它可以采用空值。 也許存儲在堆棧中的本地值的問題並不是那么重要(更多的是實現細節而不是其他任何東西),但其余部分是如何定義的。

Nullable<T>定義為

struct Nullable<T>
{
    private bool hasValue;
    internal T value;
    /* methods and properties I won't go into here */
}

從這一點來看,大多數實施都是顯而易見的。 需要進行一些特殊處理,允許為其分配null - 視為已分配default(Nullable<T>) - 以及裝箱時的一些特殊處理,然后是其余的(包括可以與null進行比較) )。

如果Nullable<T>是一個引用類型,那么我們必須進行特殊處理以允許所有其余的發生,以及.NET如何幫助開發人員的特殊處理(例如我們需要特殊處理)使它從ValueType下降)。 我甚至不確定它是否可能。

*我們如何被允許重新定義平等有一些限制。 將這些規則與默認值中使用的規則組合在一起,通常我們可以允許兩個值被認為是相等的,默認情況下會被認為是不相等的,但是考慮兩個不等於默認值相等的值是很有意義的。 一個例外是struct只包含value-types,但所說的value-types重新定義了相等。 這是優化的結果,通常被認為是錯誤而不是設計。

†異常是浮點類型。 由於CLI標准中值類型的定義, double.NaN.Equals(double.NaN)float.NaN.Equals(float.NaN)返回true 但由於ISO 60559中NaN的定義, float.NaN == float.NaNdouble.NaN == double.NaN都返回false。

編輯以解決更新的問題......

如果要將結構用作參考,則可以裝箱和取消裝箱對象。

但是, Nullable<>類型基本上允許使用附加狀態標志來增強任何值類型,該狀態標志指示該值是否應該用作null或者stuct是否為“valid”。

所以要解決你的問題:

  1. 在集合中使用時,或者由於不同的語義(復制而不是引用),這是一個優勢

  2. 不,不。 當裝箱和拆箱時,CLR會尊重這一點,因此您實際上從不Nullable<>實例。 拳擊Nullable<>哪個“有”沒有值將返回一個null引用,而拆箱則相反。

  3. 不。

  4. 同樣,情況並非如此。 實際上,struct的泛型約束不允許使用可為空的結構。 由於特殊的裝箱/拆箱行為,這是有道理的。 因此,如果您有一個where T: struct來約束泛型類型,則不允許使用可空類型。 由於此約束也是在Nullable<T>類型上定義的,因此您無法嵌套它們,無需任何特殊處理來防止這種情況。

為什么不使用參考? 我已經提到了重要的語義差異。 但除此之外,引用類型使用更多的內存空間:每個引用,特別是在64位環境中,不僅用於實例的堆內存,還用於引用的內存,實例類型信息,鎖定位等。因此,除了語義和性能差異(通過引用間接)之外,您最終會使用用於實體本身的多個內存用於大多數常見實體。 GC可以處理更多的對象,這將使整體性能與結構相比更加糟糕。

它不可變; 再檢查一遍。

拳擊也不同; 一個空的“盒子”為null。

但; 它很小(幾乎不大於T),不可變,只封裝結構 - 理想的結構。 也許更重要的是,只要T真的是一個“價值”,那么T也是如此嗎? 邏輯“價值”。

我將MyNullable編碼為一個類。 無法真正理解為什么它不能成為一個類,旁邊避免堆內存壓力。

namespace ClassLibrary1

{使用NFluent;

using NUnit.Framework;

[TestFixture]
class MyNullableShould
{
    [Test]
    public void operator_equals_btw_nullable_and_value_works()
    {
        var myNullable = new MyNullable<int>(1);

        Check.That(myNullable == 1).IsEqualTo(true);
        Check.That(myNullable == 2).IsEqualTo(false);
    }

    [Test]
    public void Can_be_comparedi_with_operator_equal_equals()
    {
        var myNullable = new MyNullable<int>(1);
        var myNullable2 = new MyNullable<int>(1);

        Check.That(myNullable == myNullable2).IsTrue();
        Check.That(myNullable == myNullable2).IsTrue();

        var myNullable3 = new MyNullable<int>(2);
        Check.That(myNullable == myNullable3).IsFalse();
    }
}

} namespace ClassLibrary1 {using System;

public class MyNullable<T> where T : struct
{
    internal T value;

    public MyNullable(T value)
    {
        this.value = value;
        this.HasValue = true;
    }

    public bool HasValue { get; }

    public T Value
    {
        get
        {
            if (!this.HasValue) throw new Exception("Cannot grab value when has no value");
            return this.value;
        }
    }

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

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

    public static bool operator ==(MyNullable<T> n1, MyNullable<T> n2)
    {
        if (!n1.HasValue) return !n2.HasValue;
        if (!n2.HasValue) return false;
        return Equals(n1.value, n2.value);
    }

    public static bool operator !=(MyNullable<T> n1, MyNullable<T> n2)
    {
        return !(n1 == n2);
    }

    public override bool Equals(object other)
    {
        if (!this.HasValue) return other == null;
        if (other == null) return false;
        return this.value.Equals(other);
    }

    public override int GetHashCode()
    {
        return this.HasValue ? this.value.GetHashCode() : 0;
    }

    public T GetValueOrDefault()
    {
        return this.value;
    }

    public T GetValueOrDefault(T defaultValue)
    {
        return this.HasValue ? this.value : defaultValue;
    }

    public override string ToString()
    {
        return this.HasValue ? this.value.ToString() : string.Empty;
    }
}

}

暫無
暫無

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

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