什么时候应该在C#中使用结构而不是类?我的概念模型是,当项只是值类型的集合时,使用结构。一种将它们逻辑地结合在一起的方法。
我在这里遇到了这些规则:
结构应表示单个价值结构应具有内存占用空间小于16字节。结构不应在之后更改创造
这些规则有效吗?结构在语义上意味着什么?
什么时候应该在C#中使用结构而不是类?我的概念模型是,当项只是值类型的集合时,使用结构。一种将它们逻辑地结合在一起的方法。
我在这里遇到了这些规则:
结构应表示单个价值结构应具有内存占用空间小于16字节。结构不应在之后更改创造
这些规则有效吗?结构在语义上意味着什么?
当前回答
结构在大多数方面类似于类/对象。结构可以包含函数、成员,并且可以继承。但C#中的结构仅用于数据保存。结构比类占用更少的RAM,并且垃圾收集器更容易收集。但当您在结构中使用函数时,编译器实际上采用的结构与类/对象非常相似,所以如果您想要使用函数,请使用类/对象。
其他回答
结构是值类型。如果将结构分配给新变量,则新变量将包含原始变量的副本。
public struct IntStruct {
public int Value {get; set;}
}
执行以下操作将导致存储在内存中的结构的5个实例:
var struct1 = new IntStruct() { Value = 0 }; // original
var struct2 = struct1; // A copy is made
var struct3 = struct2; // A copy is made
var struct4 = struct3; // A copy is made
var struct5 = struct4; // A copy is made
// NOTE: A "copy" will occur when you pass a struct into a method parameter.
// To avoid the "copy", use the ref keyword.
// Although structs are designed to use less system resources
// than classes. If used incorrectly, they could use significantly more.
类是引用类型。将类分配给新变量时,该变量包含对原始类对象的引用。
public class IntClass {
public int Value {get; set;}
}
执行以下操作只会导致内存中类对象的一个实例。
var class1 = new IntClass() { Value = 0 };
var class2 = class1; // A reference is made to class1
var class3 = class2; // A reference is made to class1
var class4 = class3; // A reference is made to class1
var class5 = class4; // A reference is made to class1
结构可能会增加代码错误的可能性。如果将值对象视为可变引用对象,那么当所做的更改意外丢失时,开发人员可能会感到惊讶。
var struct1 = new IntStruct() { Value = 0 };
var struct2 = struct1;
struct2.Value = 1;
// At this point, a developer may be surprised when
// struct1.Value is 0 and not 1
当您需要值语义而不是引用语义时,请使用结构。
如果需要引用语义,则需要类而不是结构。
无论何时:
不需要多态性,want值语义,以及希望避免堆分配和相关的垃圾收集开销。
然而,需要注意的是,结构(任意大)传递比类引用(通常是一个机器字)更昂贵,因此类在实践中可能会更快。
OP引用的消息来源有一定的可信度。。。但微软呢?对结构使用的立场是什么?我向微软寻求了一些额外的学习,以下是我的发现:
如果类型很小,通常寿命很短,或者通常嵌入其他对象。除非类型具有以下所有特征,否则不要定义结构:它在逻辑上表示单个值,类似于基本类型(整数、双精度等)。它的实例大小小于16字节。它是不可变的。它不必经常装箱。
Microsoft一贯违反这些规则
好吧,无论如何,第二和第三。我们喜爱的字典有两个内部结构:
[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名可能不会成为问题。如果您发现自己正在装箱一个结构,请重新思考您的体系结构。
让我们来看看为什么微软会使用这些结构:
每个结构Entry和Enumerator表示单个值。速度条目永远不会作为Dictionary类之外的参数传递。进一步的调查表明,为了满足IEnumerable的实现,Dictionary使用了每次请求枚举器时都会复制的枚举器结构。。。有道理。Dictionary类的内部。枚举器是公共的,因为Dictionary是可枚举的,并且必须对IEnumerator接口实现(例如IEnumeratorgetter)具有同等的可访问性。
更新-此外,请注意,当一个结构实现了一个接口(如Enumerator)并被强制转换为该实现的类型时,该结构将成为一个引用类型并被移动到堆中。在Dictionary类内部,Enumerator仍然是值类型。然而,一旦方法调用GetEnumerator(),就会返回一个引用类型IEnumerator。
我们在这里没有看到任何保持结构不可变或保持实例大小仅为16字节或更少的尝试或证明:
上面的结构中没有任何内容声明为只读-不是不可变的这些结构的大小可能远远超过16字节条目具有未确定的生存期(从Add()到Remove()、Clear()或垃圾收集);
和4.两个结构都存储TKey和TValue,我们都知道它们非常适合作为引用类型(添加了额外的信息)
尽管有哈希键,但字典速度很快,部分原因是实例化结构比引用类型更快。这里,我有一个Dictionary<int,int>,它存储了300000个随机整数和顺序递增的键。
容量:312874内存大小:2660827字节完成调整大小:5ms填充总时间:889ms
容量:必须调整内部数组大小之前可用的元素数。
MemSize:通过将字典序列化为MemoryStream并获得字节长度(对于我们的目的来说足够精确)来确定。
完成调整大小:将内部数组从150862个元素调整为312874个元素所需的时间。如果您认为每个元素都是通过Array.CopyTo()顺序复制的,那就不太糟糕了。
填充总时间:由于日志记录和我添加到源中的OnResize事件,确实存在偏差;然而,在操作期间填充300k个整数并调整大小15次仍然令人印象深刻。只是出于好奇,如果我已经知道容量,那么总的填充时间是多少?13毫秒
那么,现在,如果Entry是一个类呢?这些时间或指标真的会有那么大的不同吗?
容量:312874内存大小:2660827字节完成调整大小:26ms填充总时间:964ms
显然,最大的区别在于调整大小。如果字典是用容量初始化的,有什么区别吗?不够关心。。。12毫秒。
所发生的是,由于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
…我们从中得到了什么:简而言之,对价值类型的使用负责。它们快速高效,但如果维护不当(即无意复制),则能够导致许多意外行为。
✔️ 考虑结构使用
创建一个对象或不需要创建该对象(您可以直接赋值,它创建对象)需要提高速度或性能无需施工人员和拆卸人员(静态承包商可用)不需要类继承,但可以接受接口工作负载小的对象工作,如果工作负载高,内存问题将增加不能为变量设置默认值。结构还可以使用方法、事件、静态构造函数、变量等GC中的工作量更少不需要引用类型,只需要值类型(每次创建新对象时)无不可变对象(字符串是不可变对象,因为任何操作都不会在不更改原始字符串的情况下每次返回新字符串)