[英]Why Nullable<T> is a struct?
我想知道为什么Nullable<T>
是一个值类型,如果它被设计为模仿引用类型的行为? 我理解GC压力之类的东西,但我不相信 - 如果我们想让int
表现得像参考,我们可能会对所有具有真实参考类型的后果感到满意。 我看不出为什么Nullable<T>
不仅仅是T
struct的盒装版本。
作为价值类型:
null
值一样的null
值可null
) Equals
完成,没有真正的问题) 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.NaN
和double.NaN == double.NaN
都返回false。
编辑以解决更新的问题......
如果要将结构用作参考,则可以装箱和取消装箱对象。
但是, Nullable<>
类型基本上允许使用附加状态标志来增强任何值类型,该状态标志指示该值是否应该用作null
或者stuct是否为“valid”。
所以要解决你的问题:
在集合中使用时,或者由于不同的语义(复制而不是引用),这是一个优势
不,不。 当装箱和拆箱时,CLR会尊重这一点,因此您实际上从不Nullable<>
实例。 拳击Nullable<>
哪个“有”没有值将返回一个null
引用,而拆箱则相反。
不。
同样,情况并非如此。 实际上,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.