C# 为什么为空<;T>;是一个结构吗?

C# 为什么为空<;T>;是一个结构吗?,c#,clr,nullable,C#,Clr,Nullable,我想知道为什么Nullable是一个值类型,如果它被设计成模仿引用类型的行为的话?我理解GC压力之类的事情,但我不觉得有说服力——如果我们想让int像引用一样工作,我们可能可以接受使用真实引用类型的所有后果。我看不出为什么Nullable不仅仅是Tstruct的盒装版本 作为值类型: 它仍然需要装箱和取消装箱,而且装箱必须与“普通”结构有所不同(要像对待实null一样对待空值null) 在检查null时,需要对其进行不同的处理(只需在Equals中完成,没有实际问题) 它是可变的,打破了结构应该

我想知道为什么
Nullable
是一个值类型,如果它被设计成模仿引用类型的行为的话?我理解GC压力之类的事情,但我不觉得有说服力——如果我们想让
int
像引用一样工作,我们可能可以接受使用真实引用类型的所有后果。我看不出为什么
Nullable
不仅仅是
T
struct的盒装版本

作为值类型:

  • 它仍然需要装箱和取消装箱,而且装箱必须与“普通”结构有所不同(要像对待实
    null
    一样对待空值null)
  • 在检查null时,需要对其进行不同的处理(只需在
    Equals
    中完成,没有实际问题)
  • 它是可变的,打破了结构应该是不可变的规则(好的,它在逻辑上是不可变的)
  • 它需要有特殊的限制来禁止递归,比如
    Nullable
  • 设为可空
    引用类型难道不能解决这个问题吗

    重新措辞并更新:

    我对我的理由列表做了一些修改,但我的一般问题仍然没有解决:

    引用类型
    Nullable
    如何比当前值类型实现更糟糕?这仅仅是GC压力和“小而不变”的规则吗?这对我来说仍然很奇怪…

    编辑以解决更新的问题

    如果要将结构用作引用,可以装箱和取消装箱对象

    但是,
    Nullable
    类型基本上允许使用一个附加的状态标志来增强任何值类型,该状态标志指示该值是否应作为
    null
    使用,或者该值是否“有效”

    因此,为了回答您的问题:

  • 在集合中使用时,或者由于语义不同(复制而不是引用),这是一个优势

  • 不,没有。CLR在装箱和取消装箱时确实尊重这一点,因此实际上您永远不会装箱可为null的
    实例。装箱一个“没有”值的
    null
    将返回一个
    null
    引用,而取消装箱则相反

  • 没有

  • 再说一次,情况并非如此。事实上,结构的泛型约束不允许使用可为空的结构。由于特殊的装箱/拆箱行为,这是有意义的。因此,如果您使用
    where T:struct
    约束泛型类型,则不允许使用可为空的类型。由于此约束也定义在
    Nullable
    类型上,因此如果不进行任何特殊处理,就无法嵌套它们


  • 为什么不使用参考资料?我已经提到了重要的语义差异。但除此之外,引用类型使用了更多的内存空间:每个引用,特别是在64位环境中,不仅会占用实例的堆内存,还会占用引用的内存、实例类型信息、锁定位等。。因此,除了语义和性能差异(通过引用间接寻址)之外,对于大多数常见的实体,您最终将使用实体本身使用的内存的倍数。GC需要处理更多的对象,这将使总体性能比结构更差。

    它是不可变的;再查一遍

    拳击也不一样;将空“框”设置为空


    但是;它很小(略大于T),不可变,并且只封装结构——理想的结构。也许更重要的是,只要T是一个真正的“值”,那么T也是吗?逻辑“值”。

    原因是它不是设计成一个引用类型。它被设计成一个值类型,除了一个特定的类型。让我们看看值类型和引用类型的一些不同之处

    值和引用类型之间的主要区别在于,值类型是自包含的(包含实际值的变量),而引用类型引用另一个值

    这还带来了其他一些差异。事实上,我们可以直接别名引用类型(它有好的和坏的影响),这就是原因。平等的含义也存在差异:

    值类型具有基于包含的值的相等概念,可以选择重新定义该值(对于如何进行重新定义存在逻辑限制*)。引用类型的标识概念对于无法重新定义的值类型没有意义(因为它们不能直接别名,所以两个这样的值不能相同),这也为其相等概念提供了默认值。默认情况下,
    =
    在值类型†上处理基于值的相等,但在引用类型上处理基于标识的相等。此外,即使引用类型被赋予了基于值的相等概念,并将其用于
    =
    ,它也不会失去与另一个标识引用进行比较的能力

    由此产生的另一个区别是引用类型可以为null-引用另一个值的值允许不引用任何值的值,这就是null引用

    此外,保持值类型较小的一些优点与此相关,因为它们基于值,在传递给函数时会按值复制

    其他一些差异是隐含的,但不是由此产生的。使值类型不可变通常是一个好主意,这是隐含的,但不是核心差异所带来的,因为虽然在不考虑实现问题的情况下可以找到一些优势,但在引用类型中这样做也有一些优势(实际上,一些与别名安全相关的优势更直接地应用于引用类型)以及人们可能违反这一准则的原因——因此这不是一条硬性规定(对于嵌套值类型,所涉及的风险大大降低,因此即使我的风格非常倾向于mak,我也不会对使嵌套值类型可变感到不安)
    struct Nullable<T>
    {
        private bool hasValue;
        internal T value;
        /* methods and properties I won't go into here */
    }
    
    namespace ClassLibrary1
    
    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();
        }
    }
    
    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;
        }
    }