何时使用struct?

什么时候应该在C#中使用struct而不是类? 我的概念模型是,当项目仅仅是一个值类型集合的时候,使用了结构体。 从逻辑上把它们组合在一起成为一个有凝聚力的整体。

我在这里遇到了这些规则:

  • 结构应该代表一个单一的值。
  • 一个结构应该有一个小于16字节的内存占用。
  • 创建后不应更改结构。
  • 这些规则是否有效? 结构在语义上是什么意思?


    OP引用的源代码有一些可信度......但是微软呢 - 结构使用的立场是什么? 我向微软寻求了一些额外的学习,这里是我发现的:

    考虑定义一个结构而不是类,如果类型的实例很小并且通常是短暂的或者通常嵌入其他对象中的话。

    除非类型具有以下所有特征,否则不要定义结构:

  • 它在逻辑上代表一个单值,类似于原始类型(整数,双精度等)。
  • 它的实例大小小于16个字节。
  • 它是不可变的。
  • 它不会经常被装箱。
  • 微软一贯违反这些规则

    好的,无论如何,#2和#3。 我们心爱的字典有两个内部结构:

    [StructLayout(LayoutKind.Sequential)]  // default for structs
    private struct Entry  //<Tkey, TValue>
    {
        //  View code at *Reference Source
    }
    
    [Serializable, StructLayout(LayoutKind.Sequential)]
    public struct Enumerator : 
        IEnumerator<KeyValuePair<TKey, TValue>>, IDisposable, 
        IDictionaryEnumerator, IEnumerator
    {
        //  View code at *Reference Source
    }
    

    *参考源

    'JonnyCantCode.com'的来源得到了4分之3 - 相当可原谅,因为#4可能不会成为问题。 如果你发现自己装一个结构,重新考虑你的架构。

    让我们看看为什么微软会使用这些结构:

  • 每个结构EntryEnumerator代表单个值。
  • 速度
  • Entry不会作为Dictionary类之外的参数传递。 进一步的调查显示,为了满足IEnumerable的实现,Dictionary使用Enumerator结构,它在每次请求枚举器时都会复制它......这是有道理的。
  • Dictionary类的内部。 Enumerator是公开的,因为Dictionary是可枚举的,并且必须具有对IEnumerator接口实现的平等可访问性 - 例如IEnumerator getter。
  • 更新 - 另外,要意识到当一个结构实现一个接口(就像Enumerator所做的那样)并且被转换为该实现的类型时,该结构将成为一个引用类型并被移动到堆中。 在Dictionary类的内部,Enumerator仍然是一个值类型。 但是,只要方法调用GetEnumerator() ,就会返回一个引用类型的IEnumerator

    我们在这里没有看到任何企图或证明需要保持结构不变或保持仅16字节或更小的实例大小:

  • 上面的结构中没有任何内容是readonly - 不是不可变的
  • 这些结构的大小可以超过16个字节
  • Entry具有未确定的生命周期(从Add()Remove()Clear()或垃圾收集);
  • 并且... 4.这两个结构都存储了TKey和TValue,我们都知道它们可以作为参考类型(额外的奖励信息)

    尽管有哈希键,但字典的部分速度很快,因为实例化结构比参考类型快。 在这里,我有一个Dictionary<int, int> ,它用循序递增的键存储300,000个随机整数。

    容量:312874
    MemSize:2660827字节
    完成调整大小:5ms
    总填充时间:889ms

    容量 :内部数组必须调整大小之前可用的元素数量。

    MemSize :通过将字典序列化成MemoryStream并获取字节长度(足够精确,用于我们的目的)确定。

    完成调整大小 :将内部数组从150862个元素调整为312874个元素所需的时间。 当你发现每个元素都是通过Array.CopyTo()顺序复制的Array.CopyTo() ,这并不是太简单。

    总填充时间 :由于日志记录和我添加到源的OnResize事件而OnResize倾斜; 然而,在操作期间重新调整15次的同时仍然可以填充30万整数。 出于好奇,如果我已经知道这个容量,总共需要多长时间? 13毫秒

    那么,现在,如果Entry是一个班级呢? 这些时间或指标真的会有很大差异吗?

    容量:312874
    MemSize:2660827字节
    完成调整大小:26ms
    总共需要964ms

    显然,最大的不同在于调整大小。 如果字典使用容量进行初始化,有什么区别? 不足以关心...... 12ms

    会发生什么是因为Entry是一个结构,它不需要像引用类型那样进行初始化。 这既是价值类型的美丽也是祸害。 为了使用Entry作为引用类型,我必须插入以下代码:

    /*
     *  Added to satisfy initialization of entry elements --
     *  this is where the extra time is spent resizing the Entry array
     * **/
    for (int i = 0 ; i < prime ; i++)
    {
        destinationArray[i] = new Entry( );
    }
    /*  *********************************************** */  
    

    我必须将Entry每个数组元素初始化为引用类型的原因可以在MSDN:Structure Design中找到。 简而言之:

    不要为结构提供默认构造函数。

    如果一个结构定义了一个默认的构造函数,那么当创建该结构的数组时,公共语言运行时会自动在每个数组元素上执行默认的构造函数。

    一些编译器(如C#编译器)不允许结构具有默认构造函数。

    这其实很简单,我们将借鉴阿西莫夫的“机器人三定律”:

  • 该结构必须安全使用
  • 结构必须有效地执行其功能,除非这会违反规则#1
  • 该结构在使用过程中必须保持完整,除非其需要满足规则#1
  • ......我们从中得到了什么:简而言之,要对价值类型的使用负责。 他们快速高效,但如果维护不当,有能力引发许多意想不到的行为(即无意复制)。


    每当你不需要多态时,需要值语义,并且希望避免堆分配和相关的垃圾收集开销。 然而,需要注意的是,结构(任意大)比阶级参考(通常是一个机器词)更昂贵,所以阶级最终可能在实践中更快。


    我不同意原帖中的规定。 这是我的规则:

    1)存储在数组中时,可以使用结构来实现性能。 (另请参见何时结构答案?)

    2)您需要在代码中将结构化数据传递到C / C ++

    3)除非你需要结构,否则不要使用结构:

  • 它们在分配和作为参数传递时会与“正常对象”(引用类型)不同,这会导致意外的行为; 如果查看代码的人不知道他们正在处理一个结构,这是特别危险的。
  • 它们不能被继承。
  • 传递结构作为参数比类更昂贵。
  • 链接地址: http://www.djcxy.com/p/4807.html

    上一篇: When to use struct?

    下一篇: NullReferenceException thrown when testing custom AuthorizationAttribute