简体   繁体   English

为什么Nullable <T> 是一个结构?

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

I was wondering why Nullable<T> is a value type, if it is designed to mimic the behavior of reference types? 我想知道为什么Nullable<T>是一个值类型,如果它被设计为模仿引用类型的行为? I understand things like GC pressure, but I don't feel convinced - if we want to have int acting like reference, we are probably OK with all the consequences of having real reference type. 我理解GC压力之类的东西,但我不相信 - 如果我们想让int表现得像参考,我们可能会对所有具有真实参考类型的后果感到满意。 I can see no reason why Nullable<T> is not just boxed version of T struct. 我看不出为什么Nullable<T>不仅仅是T struct的盒装版本。

As value type: 作为价值类型:

  1. it still needs to be boxed and unboxed, and more, boxing must be a bit different than with "normal" structs (to treat null-valued nullable like real null ) 它仍然需要装箱和取消装箱,而且更多,装箱必须与“普通”结构有点不同(对待像null值一样的null值可null
  2. it needs to be treated differently when checking for null (done simply in Equals , no real problem) 检查null时需要区别对待 (仅在Equals完成,没有真正的问题)
  3. it is mutable, breaking the rule that structs should be immutable (ok, it is logically immutable) 它是可变的,打破了结构应该是不可变的规则 (好吧,它在逻辑上是不可变的)
  4. it needs to have special restriction to disallow recursion like Nullable<Nullable<T>> 它需要有特殊限制来禁止Nullable<Nullable<T>>等递归

Doesn't making Nullable<T> a reference type solve that issues? 是不是使Nullable<T>成为引用类型解决问题?

rephrased and updated: 改写和更新:

I've modified my reason list a bit, but my general question is still open: 我已经修改了我的理由列表,但我的一般问题仍然是开放的:

How will reference type Nullable<T> be worse than current value type implementation? 引用类型Nullable<T>将如何比当前值类型实现更糟糕? Is it only GC pressure and "small, immutable" rule? 这只是GC压力和“小的,不可改变的”规则吗? It still feels strange for me... 它对我来说仍然很奇怪......

The reason is that it was not designed to act like a reference type. 其原因是,它不是设计来像一个引用类型。 It was designed to act like a value type, except in just one particular. 它的设计就像一个值类型,除了一个特定的类型。 Let's look at some ways value types and reference types differ. 让我们看一下值类型和引用类型的不同之处。

The main difference between a value and reference type, is that value type is self-contained (the variable containing the actual value), while a reference type refers to another value. 值和引用类型之间的主要区别在于值类型是自包含的(包含实际值的变量),而引用类型是指另一个值。

Some other differences are entailed by this. 其他一些差异也是由此引起的。 The fact that we can alias reference types directly (which has both good and bad effects) comes from this. 我们可以直接别名引用类型(具有好的和坏的效果)的事实来自于此。 So too do differences in what equality means: 平等意味着什么呢?

A value type has a concept of equality based on the value contained, which can optionally be redefined (there are logical restrictions on how this redefinition can happen*). 值类型具有基于所包含的值的相等概念,可以选择性地重新定义(对重新定义的发生方式存在逻辑限制*)。 A reference type has a concept of identity that is meaningless with value types (as they cannot be directly aliased, so two such values cannot be identical) that can not be redefined, which is also gives the default for its concept of equality. 引用类型具有对于值类型无意义的标识概念(因为它们不能直接别名,因此两个这样的值不能相同),这些值无法重新定义,这也是其相等概念的默认值。 By default, == deals with this value-based equality when it comes to value types†, but with identity when it comes to reference types. 默认情况下, ==在涉及值类型†时处理这种基于值的相等性,但在引用类型时具有标识。 Also, even when a reference type is given a value-based concept of equality, and has it used for == it never loses the ability to be compared to another reference for identity. 此外,即使参考类型被赋予基于值的相等概念,并且它用于==它也永远不会失去与另一个身份参考进行比较的能力。

Another difference entailed by this is that reference types can be null - a value that refers to another value allows for a value that doesn't refer to any value, which is what a null reference is. 这样做的另一个区别是引用类型可以为null - 引用另一个值的值允许一个不引用任何值的值,这是一个空引用。

Also, some of the advantages of keeping value-types small relate to this, since being based on value, they are copied by value when passed to functions. 此外,保持值类型较小的一些优点与此相关,因为基于值,它们在传递给函数时按值复制。

Some other differences are implied but not entailed by this. 其他一些差异是暗示的,但不是由此引起的。 That it's often a good idea to make value types immutable is implied but not entailed by the core difference because while there are advantages to be found without considering implementation matters, there are also advantages in doing so with reference types (indeed some relating to safety with aliases apply more immediately to reference types) and reasons why one may break this guideline - so it's not a hard and fast rule (with nested value types the risks involved are so heavily reduced that I would have few qualms in making a nested value type mutable, even though my style leans heavily to making even reference types immutable when at all practical). 使价值类型不可变的通常是一个好主意是隐含的但不是由核心差异所引起的,因为虽然在不考虑实施问题的情况下可以找到优势,但使用引用类型(实际上与安全性有关)也有一些优势。别名更直接地应用于引用类型)以及可能违反此指南的原因 - 因此它不是一个硬性规则(使用嵌套值类型,所涉及的风险大大减少,以至于我在使嵌套值类型变为可变时几乎没有疑虑,即使我的风格很大程度上倾向于使偶数引用类型在实际中不可变。

Some further differences between value types and reference types are arguably implementation details. 值类型和引用类型之间的一些进一步差异可以说是实现细节。 That a value type in a local variable has the value stored on the stack has been argued as an implementation detail; 局部变量中的值类型具有存储在堆栈中的值已被认为是实现细节; probably a pretty obvious one if your implementation has a stack, and certainly an important one in some cases, but not core to the definition. 如果你的实现有一个堆栈,可能是一个非常明显的一个,在某些情况下肯定是一个重要的,但不是定义的核心。 It's also often overstated (for a start, a reference type in a local variable also has the reference itself in the stack, for another there are plenty of times when a value type value is stored in the heap). 它也经常被夸大(对于一个开始,局部变量中的引用类型也在栈中具有引用本身,而另一个有很多时候值类型值存储在堆中)。

Some further advantages in value types being small relate to this. 值类型的一些进一步优点是与此相关。


Now, Nullable<T> is a type that behaves like a value type in all the ways described above, except that it can take a null value. 现在, Nullable<T>是一种类型,其行为类似于上面描述的所有方式的值类型,除了它可以采用空值。 Maybe the matter of local values being stored on the stack isn't all that important (being more an implementation detail than anything else), but the rest is inherent to how it is defined. 也许存储在堆栈中的本地值的问题并不是那么重要(更多的是实现细节而不是其他任何东西),但其余部分是如何定义的。

Nullable<T> is defined as Nullable<T>定义为

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

Most of the implementation from this point is obvious. 从这一点来看,大多数实施都是显而易见的。 Some special handling is needed allow null to be assigned to it - treated as if default(Nullable<T>) had been assigned - and some special handling when boxed, and then the rest follows (including that it can be compared for equality with null). 需要进行一些特殊处理,允许为其分配null - 视为已分配default(Nullable<T>) - 以及装箱时的一些特殊处理,然后是其余的(包括可以与null进行比较) )。

If Nullable<T> was a reference type, then we'd have to have special handling to allow for all the rest to occur, along with special handling for features in how .NET helps the developer (such as we'd need special handling to make it descend from ValueType ). 如果Nullable<T>是一个引用类型,那么我们必须进行特殊处理以允许所有其余的发生,以及.NET如何帮助开发人员的特殊处理(例如我们需要特殊处理)使它从ValueType下降)。 I'm not even sure if it would be possible. 我甚至不确定它是否可能。

*There are some restrictions on how we are allowed to redefine equality. *我们如何被允许重新定义平等有一些限制。 Combining those rules with those used in the defaults, then generally we can allow for two values to be considered equal that would be considered unequal by default, but it rarely makes sense to consider two values unequal that the default would consider equal. 将这些规则与默认值中使用的规则组合在一起,通常我们可以允许两个值被认为是相等的,默认情况下会被认为是不相等的,但是考虑两个不等于默认值相等的值是很有意义的。 A exception is the case where a struct contains only value-types, but where said value-types redefine equality. 一个例外是struct只包含value-types,但所说的value-types重新定义了相等。 This the a result of an optimisation, and generally considered a bug rather than by design. 这是优化的结果,通常被认为是错误而不是设计。

†An exception is float-point types. †异常是浮点类型。 Because of the definition of value-types in the CLI standard, double.NaN.Equals(double.NaN) and float.NaN.Equals(float.NaN) return true . 由于CLI标准中值类型的定义, double.NaN.Equals(double.NaN)float.NaN.Equals(float.NaN)返回true But because of the definition of NaN in ISO 60559, float.NaN == float.NaN and double.NaN == double.NaN both return false. 但由于ISO 60559中NaN的定义, float.NaN == float.NaNdouble.NaN == double.NaN都返回false。

Edited to address the updated question... 编辑以解决更新的问题......

You can box and unbox objects if you want to use a struct as a reference. 如果要将结构用作参考,则可以装箱和取消装箱对象。

However, the Nullable<> type basically allows to enhance any value type with an additional state flag which tells whether the value shall be used as null or if the stuct is "valid". 但是, Nullable<>类型基本上允许使用附加状态标志来增强任何值类型,该状态标志指示该值是否应该用作null或者stuct是否为“valid”。

So to address your questions: 所以要解决你的问题:

  1. This is an advantage when used in collections, or because of the different semantics (copying instead of referencing) 在集合中使用时,或者由于不同的语义(复制而不是引用),这是一个优势

  2. No it doesn't. 不,不。 The CLR does respect this when boxing and unboxing, so that you actually never box a Nullable<> instance. 当装箱和拆箱时,CLR会尊重这一点,因此您实际上从不Nullable<>实例。 Boxing a Nullable<> which "has" no value will return a null reference, and unboxing does the opposite. 拳击Nullable<>哪个“有”没有值将返回一个null引用,而拆箱则相反。

  3. Nope. 不。

  4. Again, this isn't the case. 同样,情况并非如此。 In fact generic constraints for a struct do not allow nullable structs to be used. 实际上,struct的泛型约束不允许使用可为空的结构。 This makes sense due to the special boxing/unboxing behavior. 由于特殊的装箱/拆箱行为,这是有道理的。 Therefore, if you have a where T: struct to constrain a generic type, nullable types will be disallowed. 因此,如果您有一个where T: struct来约束泛型类型,则不允许使用可空类型。 Since this constraint is defined on the Nullable<T> type as well, you cannot nest them, without any special treatment to prevent this. 由于此约束也是在Nullable<T>类型上定义的,因此您无法嵌套它们,无需任何特殊处理来防止这种情况。

Why not using references? 为什么不使用参考? I already mentioned the important semantic differences. 我已经提到了重要的语义差异。 But apart from this, reference types use much more memory space: Each reference, especially in 64-bit environments, uses up not only heap memory for the instance, but also memory for the reference, the instance type information, locking bits etc.. So, apart from the semantics and performance differences (indirection via reference), you end up with using a multiple of the memory used for the entity itself for most common entities. 但除此之外,引用类型使用更多的内存空间:每个引用,特别是在64位环境中,不仅用于实例的堆内存,还用于引用的内存,实例类型信息,锁定位等。因此,除了语义和性能差异(通过引用间接)之外,您最终会使用用于实体本身的多个内存用于大多数常见实体。 And the GC gets more objects to handle, which will make the total performance compared to structs even worse. GC可以处理更多的对象,这将使整体性能与结构相比更加糟糕。

It is not mutable; 它不可变; check again. 再检查一遍。

The boxing is different too; 拳击也不同; an empty "boxes" to null. 一个空的“盒子”为null。

But; 但; it is small (barely bigger than T), immutable, and encapsulates only structs - ideal as a struct. 它很小(几乎不大于T),不可变,只封装结构 - 理想的结构。 Perhaps more importantly, so long as T is truly a "value", then so is T? 也许更重要的是,只要T真的是一个“价值”,那么T也是如此吗? a logical "value". 逻辑“价值”。

I coded MyNullable as a class. 我将MyNullable编码为一个类。 Can't really understand why it cannot be a class, beside for avoid heap memory pressure. 无法真正理解为什么它不能成为一个类,旁边避免堆内存压力。

namespace ClassLibrary1

{ using NFluent; {使用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; } 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