【问题标题】:In C#, are the values in a List<struct> boxed?在 C# 中, List<struct> 中的值是否已装箱?
【发布时间】:2010-08-25 21:37:35
【问题描述】:

假设我声明了一个包含结构类型值的通用列表:

struct MyStruct {
    public MyStruct(int val1, decimal val2) : this() {
        Val1 = val1;
        Val2 = val2;
    }
    public int Val1 {get; private set;}
    public decimal Val2 {get; private set;}
}

List<MyStruct> list;

List 是否将每个单独的值存储为盒装结构,在堆上单独分配?还是比这更聪明?

【问题讨论】:

    标签: c# generics


    【解决方案1】:

    没有拳击。

    “不,不会有拳击。这是泛型的主要设计目标之一。”

    http://social.msdn.microsoft.com/Forums/en-US/csharplanguage/thread/359cf58a-f53d-478e-bc31-1507e77c9454/

    "如果一个值类型用于类型 T,编译器会为该值类型专门生成一个 List&lt;T&gt; 类的实现。这意味着 List&lt;T&gt; 对象的列表元素不必在元素可以使用,并且在创建了大约 500 个列表元素后,不装箱列表元素节省的内存大于用于生成类实现的内存。”

    http://msdn.microsoft.com/en-us/library/6sh2ey19.aspx

    【讨论】:

    • “在创建大约 500 个列表元素后,保存的内存而不是装箱列表元素大于用于生成类实现的内存”是什么意思?这是某种劣势吗?
    • @MarsonMao 没有缺点。换句话说,这意味着如果 List 是愚蠢的并且它使用 box 结构,那么列表使用的内存将增加一倍,大约为 500 个元素。由于它不会对结构进行装箱,因此 500 个元素使用的内存将比使用时少得多。
    • @Marson Mao C# 中的泛型与 C++ 不同,它们并不总是为每种唯一类型生成代码。对于引用类型,它们将使用相同的底层代码,因为指针的大小不会随引用类型而改变。但是对于值类型,大小会有所不同,因此必须唯一生成一个 List 版本,这会产生内存成本,因为这意味着现在有另一个 List 代码副本。对不起,如果我不清楚。见msdn.microsoft.com/en-us/library/f4a6ta2h.aspx
    【解决方案2】:

    它没有装箱,但数据会是原始数据的副本,每次取出数据时,它都会再次复制。这往往使丢失更改变得容易。因此,您的目标应该是不要编写可变结构。除非MyStruct 实际上代表一个度量单位(或类似的),否则将其设为一个类。如果它一个度量,则使其不可变(没有公共可设置成员)。

    无论哪种方式,都不要公开字段!使用属性;-p

    例如:

    struct MyStruct {
        public MyStruct(int val1, decimal val2) {
            this.val1 = val1;
            this.val2 = val2;
        }
        private readonly int val1;
        private readonly decimal val2;
        public int Val1 {get {return val1;}}
        public decimal Val2 {get {return val2;}}
    }
    

    或者(C# 3.0):

    struct MyStruct {
        public MyStruct(int val1, decimal val2) : this() {
            Val1 = val1;
            Val2 = val2;
        }
        public int Val1 {get; private set;}
        public decimal Val2 {get; private set;}
    }
    

    【讨论】:

    • 是的,我很感激。看到几个人指出这一点,我将在原始问题中使用您的结构,将重点重新放在装箱上,而不是结构的值语义上。
    【解决方案3】:

    不,List&lt;T&gt; 不会装箱。它在内部将其值存储在一个简单的数组中:

    public class List<T> : IList<T>, ICollection<T>, IEnumerable<T>, IList, ICollection,    IEnumerable
    {
        // Fields
        private const int _defaultCapacity = 4;
        private static T[] _emptyArray;
        private T[] _items;
        private int _size;
    

    【讨论】:

      【解决方案4】:
      typeof(List<>) // or informally List`1
      

      ...是泛型类型。泛型类型或参数化类型的特殊之处在于它们以正常方式编译,但在运行时它们会根据它们的用途再次编译。

      如果您将 ValueType 放在通用列表中,例如List 运行时行为是威胁它好像它正在存储类型 int (这是按值复制)。不需要进行装箱,因为您不需要将这个 List`1 的类型视为 int 以外的类型。

      在 .NET 2.0 之前无法做到这一点,因此臭名昭著的 ArrayList 类维护了一个对象数组,这意味着 ValueType(整数或结构)必须装箱才能放置在该容器中。

      不要与完全不同的 Java 泛型混淆。 Java 泛型是用类型擦除实现的,它不会给你同样的性能提升。 Java 所做的是,一旦编译器完成类型检查,它会将所有内容转换为对象。这就是为什么您不能使用 Java 泛型进行反射,而您可以使用 .NET 泛型类型系统。

      【讨论】:

      • 我不知道运行时编译...这很有趣。 +1 也用于 Java 信息​​。
      【解决方案5】:

      List 将所有内容存储在 T 类型的数组中。没有装箱,但数组是在堆上分配的。

      【讨论】:

        【解决方案6】:

        没有拳击,这就是为什么它比非通用方式快一点。

        【讨论】:

          【解决方案7】:

          没有拳击,那是Benefits of Generics之一:

          List<int> list1 = new List<int>();    
          // No boxing, no casting:
          list1.Add(3);
          
          // Compile-time error:
          // list1.Add("It is raining in Redmond.");
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2013-10-04
            • 2014-11-02
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2020-07-04
            • 2021-01-10
            • 1970-01-01
            相关资源
            最近更新 更多