什么时候应该在C#中使用结构而不是类? [英] When should I use a struct rather than a class in C#?

查看:80
本文介绍了什么时候应该在C#中使用结构而不是类?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

何时应在C#中使用struct而不是class?我的概念模型是在仅是值类型集合时使用结构。一种将它们逻辑地结合在一起的方法。

When should you use struct and not class in C#? My conceptual model is that structs are used in times when the item is merely a collection of value types. A way to logically hold them all together into a cohesive whole.

我遇到了这些规则此处


  • 结构应表示单个
    值。

  • 结构应具有内存
    占用空间少于16个字节。

  • 在创建
    之后,不应更改结构。

这些规则有效吗?结构在语义上是什么意思?

Do these rules work? What does a struct mean semantically?

推荐答案

OP引用的源具有一定的可信度...但是Microsoft呢?对结构用法的立场是什么?我寻求了一些从Microsoft学习的方法,以下是我发现的内容:

The source referenced by the OP has some credibility ...but what about Microsoft - what is the stance on struct usage? I sought some extra learning from Microsoft, and here is what I found:


如果
类型的实例较小且通常为短寿命或通常嵌入在$中,则考虑定义结构而不是类b $ b其他对象。

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


  1. 在逻辑上代表单个值,类似于原始类型(整数,双精度等)。

  2. 实例大小小于16个字节。

  3. 它是不可变的。

  4. 不必频繁装箱。

  1. It logically represents a single value, similar to primitive types (integer, double, and so on).
  2. It has an instance size smaller than 16 bytes.
  3. It is immutable.
  4. It will not have to be boxed frequently.




Microsoft始终违反这些规则



好吧,还是#2和#3。我们钟爱的字典有2个内部结构:

Microsoft consistently violates those rules

Okay, #2 and #3 anyway. Our beloved dictionary has 2 internal structs:

[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可能不是问题。如果您发现自己正在装箱一个结构,请重新考虑您的体系结构。

The 'JonnyCantCode.com' source got 3 out of 4 - quite forgivable since #4 probably wouldn't be an issue. If you find yourself boxing a struct, rethink your architecture.

让我们看看为什么Microsoft使用这些结构:

Let's look at why Microsoft would use these structs:


  1. 每个结构, 条目枚举器表示单个值。

  2. 速度

  3. 条目永远不会作为参数传递到Dictionary类之外。进一步的调查表明,为了满足IEnumerable的实现,Dictionary使用 Enumerator 结构,该结构在每次请求枚举器时都会复制...是有道理的。

  4. Dictionary类的内部。 Enumerator 是公开的,因为Dictionary是可枚举的,并且必须具有与IEnumerator接口实现相同的可访问性-例如IEnumerator吸气剂。

  1. Each struct, Entry and Enumerator, represent single values.
  2. Speed
  3. Entry is never passed as a parameter outside of the Dictionary class. Further investigation shows that in order to satisfy implementation of IEnumerable, Dictionary uses the Enumerator struct which it copies every time an enumerator is requested ...makes sense.
  4. Internal to the Dictionary class. Enumerator is public because Dictionary is enumerable and must have equal accessibility to the IEnumerator interface implementation - e.g. IEnumerator getter.

更新-另外,请注意,当结构实现接口时(如枚举器所做的那样),并且强制转换为该实现的类型后,该结构将成为引用类型并移至堆中。在Dictionary类内部,Enumerator 仍是一个值类型。但是,只要方法调用 GetEnumerator(),就会返回引用类型 IEnumerator

Update - In addition, realize that when a struct implements an interface - as Enumerator does - and is cast to that implemented type, the struct becomes a reference type and is moved to the heap. Internal to the Dictionary class, Enumerator is still a value type. However, as soon as a method calls GetEnumerator(), a reference-type IEnumerator is returned.

我们在这里看不到的是保持结构不变或保持实例大小仅16个字节或更少的任何尝试或要求证明:

What we don't see here is any attempt or proof of requirement to keep structs immutable or maintaining an instance size of only 16 bytes or less:


  1. 以上结构中的任何内容均未声明为只读-不可变

  2. 这些结构的大小可能超过16个字节

  3. 条目的生存期没有确定(从<$ c $起) c> Add(), Remove() Clear()或垃圾收集);

  1. Nothing in the structs above is declared readonly - not immutable
  2. Size of these struct could be well over 16 bytes
  3. Entry has an undetermined lifetime (from Add(), to Remove(), Clear(), or garbage collection);

然后...
4.两种结构都存储TKey和TValue,我们都知道它们非常有能力尽管是引用类型(增加了奖励信息)

And ... 4. Both structs store TKey and TValue, which we all know are quite capable of being reference types (added bonus info)

尽管键已散列,但字典很快,部分原因是实例化结构比引用类型更快。在这里,我有一个 Dictionary< int,int> ,其中存储着300,000个具有顺序递增键的随机整数。

Hashed keys notwithstanding, dictionaries are fast in part because instancing a struct is quicker than a reference type. Here, I have a Dictionary<int, int> that stores 300,000 random integers with sequentially incremented keys.


容量:312874

MemSize:2660827字节

完成大小:5ms

填充总时间:889ms

Capacity: 312874
MemSize: 2660827 bytes
Completed Resize: 5ms
Total time to fill: 889ms

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

Capacity: number of elements available before the internal array must be resized.

MemSize :通过将字典序列化为MemoryStream并获取字节长度(对于我们的目的足够准确)来确定。

MemSize: determined by serializing the dictionary into a MemoryStream and getting a byte length (accurate enough for our purposes).

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

Completed Resize: the time it takes to resize the internal array from 150862 elements to 312874 elements. When you figure that each element is sequentially copied via Array.CopyTo(), that ain't too shabby.

总填充时间:由于日志记录和 OnResize 事件I,导致记录时间偏斜添加到源中;但是,在操作期间将其调整为15倍时,填充30万个整数仍然令人印象深刻。出于好奇,如果我已经知道处理能力,那么总的工作时间将是多少? 13ms

Total time to fill: admittedly skewed due to logging and an OnResize event I added to the source; however, still impressive to fill 300k integers while resizing 15 times during the operation. Just out of curiosity, what would the total time to fill be if I already knew the capacity? 13ms

那么,现在,如果 Entry 是一类怎么办?这些时间或指标真的有很大的不同吗?

So, now, what if Entry were a class? Would these times or metrics really differ that much?


容量:312874

MemSize:2660827字节

完成大小:26ms

填充总时间:964毫秒

Capacity: 312874
MemSize: 2660827 bytes
Completed Resize: 26ms
Total time to fill: 964ms

显然,最大的区别在于调整大小。如果使用Capacity初始化Dictionary,有什么不同? 12ms 还不够。

Obviously, the big difference is in resizing. Any difference if Dictionary is initialized with the Capacity? Not enough to be concerned with ... 12ms.

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

What happens is, because Entry is a struct, it does not require initialization like a reference type. This is both the beauty and the bane of the value type. In order to use Entry as a reference type, I had to insert the following code:

/*
 *  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( );
}
/*  *********************************************** */  

我必须初始化<的每个数组元素的原因可以在 MSDN:结构中找到作为参考类型的code>条目设计。简而言之:

The reason I had to initialize each array element of Entry as a reference type can be found at MSDN: Structure Design. In short:


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

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

If a structure defines a default constructor, when arrays of the structure are created, the common language runtime automatically executes the default constructor on each array element.

某些编译器,例如C#编译器,不允许
的结构具有默认构造函数。

Some compilers, such as the C# compiler, do not allow structures to have default constructors.

这实际上很简单,我们将借鉴 Asimov的机器人三定律

It is actually quite simple and we will borrow from Asimov's Three Laws of Robotics:


  1. 该结构必须安全使用

  2. 该结构必须有效地执行其功能,除非这会违反规则#1

  3. 该结构在使用过程中必须保持完整,除非需要销毁该结构才能满足规则#1

... 我们要从中得到什么是:简而言之,要负责使用值类型。它们是快速而有效的,但如果维护不当,则具有引起许多意外行为的能力(即无意复制)。

...what do we take away from this: in short, be responsible with the use of value types. They are quick and efficient, but have the ability to cause many unexpected behaviors if not properly maintained (i.e. unintentional copies).

这篇关于什么时候应该在C#中使用结构而不是类?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆