MSDN说当需要轻量级对象时应该使用结构。在其他情况下,结构体比类更可取吗?
有些人可能已经忘记了:
我理解结构体和类之间的技术差异,我只是对当使用结构体没有很好的感觉。
当您需要值类型语义而不是引用类型时,请使用结构。结构体是按值复制的,所以要小心!
另见以前的问题,例如:
.NET中struct和class的区别是什么?< / >
当您不需要行为,但需要比简单的数组或字典更多的结构时。
< >强跟进 这就是我对结构体的一般看法。我知道他们可以有自己的方法,但我喜欢保持精神上的整体区分
正如@Simon所说,结构体提供了“值类型”语义,所以如果你需要类似于内置数据类型的行为,可以使用结构体。由于结构体是通过复制传递的,所以要确保它们的大小很小,大约16字节。
我会在以下情况下使用结构体:
对象应该是只读的(每次传递/赋值一个结构体都会被复制)。当涉及到多线程处理时,只读对象是很棒的,因为它们在大多数情况下不需要锁定。
物体很小,寿命很短。在这种情况下,很有可能将对象分配到堆栈上,这比将其分配到托管堆上要有效得多。更重要的是,由对象分配的内存将被释放,只要它超出其作用域。换句话说,垃圾收集器的工作量更少,内存的使用效率更高。
MSDN有答案: 在类和结构之间选择 . < / p >
基本上,这个页面给你一个4项清单,并告诉你使用一个类,除非你的类型满足所有的标准。
不定义结构,除非 类型包含以下所有内容 特点:< / p > 逻辑上表示一个值,类似于基本类型 (integer, double,等等). 实例大小小于16字节。 它是不可变的。 它不需要经常装箱。
不定义结构,除非 类型包含以下所有内容 特点:< / p >
当我想要将几个值组合在一起以从方法调用中传递内容时,我总是使用结构体,但在读取这些值之后,我将不需要将它用于任何事情。只是为了保持干净。我倾向于将struct中的东西视为“扔掉的”,而将类中的东西视为更有用和“功能性的”
嗯…
我不会用垃圾收集作为支持/反对使用结构和类的论据。托管堆的工作原理很像堆栈——创建一个对象只是把它放在堆的顶部,这几乎和在堆栈上分配一样快。此外,如果一个对象存在时间很短,没有在GC周期中存活下来,那么回收是自由的,因为GC只处理仍然可访问的内存。(搜索MSDN,有一系列关于。net内存管理的文章,我只是懒得去挖掘它们)。
大多数情况下,我使用结构体时,都会后悔这样做,因为我后来发现使用引用语义会让事情更简单一些。
无论如何,上面的MSDN文章中的这四点似乎是一个很好的指导方针。
我很惊讶我没有读到之前的答案,我认为这是最关键的方面:
当我想要一个没有标识的类型时,我使用结构体。例如一个3D点:
public struct ThreeDimensionalPoint { public readonly int X, Y, Z; public ThreeDimensionalPoint(int x, int y, int z) { this.X = x; this.Y = y; this.Z = z; } public override string ToString() { return "(X=" + this.X + ", Y=" + this.Y + ", Z=" + this.Z + ")"; } public override int GetHashCode() { return (this.X + 2) ^ (this.Y + 2) ^ (this.Z + 2); } public override bool Equals(object obj) { if (!(obj is ThreeDimensionalPoint)) return false; ThreeDimensionalPoint other = (ThreeDimensionalPoint)obj; return this == other; } public static bool operator ==(ThreeDimensionalPoint p1, ThreeDimensionalPoint p2) { return p1.X == p2.X && p1.Y == p2.Y && p1.Z == p2.Z; } public static bool operator !=(ThreeDimensionalPoint p1, ThreeDimensionalPoint p2) { return !(p1 == p2); } }
如果你有这个结构体的两个实例,你不关心它们是内存中的单个数据还是两个。你只关心他们持有的价值。
Bill Wagner在他的书“effective c#”(http://www.amazon.com/Effective-Specific-Ways-Improve-Your/dp/0321245660)中有一章关于这一点。他总结了以下原则:
类型数据存储的主要职责是什么? 它的公共接口是否完全由访问或修改其数据成员的属性定义? 你确定你的类型永远不会有子类吗? 您确定您的类型永远不会被多态处理吗? 如果4个问题都回答“是”,请使用结构体。否则,使用 类。< / p >
如果一个实体将是不可变的,那么使用结构体还是类的问题通常是性能问题,而不是语义问题。在32/64位系统上,不管类中有多少信息,类引用都需要4/8字节来存储;复制一个类引用需要复制4/8个字节。另一方面,每个截然不同的类实例将有8/16字节的开销,除了它持有的信息和对它的引用的内存开销。假设需要一个包含500个实体的数组,每个实体包含4个32位整数。如果实体是结构类型,则无论所有500个实体是否完全相同、完全不同或介于两者之间,数组都将需要8,000字节。如果实体是类类型,则包含500个引用的数组将占用4,000字节。如果这些引用都指向不同的对象,则每个对象将需要额外的24个字节(所有500个对象需要12,000个字节),总共16,000个字节——是结构类型存储成本的两倍。另一方面,在创建一个对象实例并将引用复制到所有500个数组插槽的代码中,该实例的总开销为24字节,数组的总开销为4,000字节——总共为4,024字节。节省了一大笔钱。很少有情况会像最后一种情况那样好,但在某些情况下,可能会将一些引用复制到足够多的数组槽,以使这种共享有价值。
如果实体应该是可变的,那么使用类还是结构的问题在某些方面会更容易。假设“Thing”是一个具有整数字段x的结构体或类,并执行以下代码:
Thing t1,t2; ... t2 = t1; t2.x = 5;
是否希望后一种说法影响t1.x?
如果Thing是类类型,t1和t2将等效,即t1。X和t2。X也是等价的。因此,第二个语句将影响t1.x。如果Thing是一个结构类型,t1和t2将是不同的实例,即t1。X和t2。X表示不同的整数。因此,第二个语句不会影响t1.x。
可变结构和可变类具有根本不同的行为,尽管.net在处理结构突变方面有一些怪癖。如果你想要值类型的行为(这意味着“t2=t1”将把数据从t1复制到t2,而把t1和t2作为不同的实例),如果你可以接受。net处理值类型的怪癖,那么就使用结构。如果你想要值类型语义,但.net的怪癖会导致应用程序中的值类型语义被破坏,那就使用一个类。
除了以上精彩的回答:
结构是值类型。
它们永远不能被设置为没有什么。
设置一个结构= Nothing,将其所有的值类型设置为默认值。
结构体在堆栈上而不是堆上因此它们是线程安全的,应该在实现传输对象模式时使用,你永远不想在堆上使用对象它们是易变的,在这种情况下你想使用调用堆栈,这是使用结构体的基本情况我对这里的答案感到惊讶,
使用一个类如果:
使用结构if:
我认为最好的答案就是当你需要的是属性的集合时使用struct,当你需要的是属性和行为的集合时使用class。
这是一个老话题,但希望提供一个简单的基准测试。
我已经创建了两个。cs文件:
public class TestClass { public long ID { get; set; } public string FirstName { get; set; } public string LastName { get; set; } }
而且
public struct TestStruct { public long ID { get; set; } public string FirstName { get; set; } public string LastName { get; set; } }
运行基准:
结果:
BenchmarkDotNet=v0.12.0, OS=Windows 10.0.18362 Intel Core i5-8250U CPU 1.60GHz (Kaby Lake R), 1 CPU, 8 logical and 4 physical cores .NET Core SDK=3.1.101 [Host] : .NET Core 3.1.1 (CoreCLR 4.700.19.60701, CoreFX 4.700.19.60801), X64 RyuJIT [AttachedDebugger] DefaultJob : .NET Core 3.1.1 (CoreCLR 4.700.19.60701, CoreFX 4.700.19.60801), X64 RyuJIT | Method | Mean | Error | StdDev | Ratio | RatioSD | Rank | Gen 0 | Gen 1 | Gen 2 | Allocated | |--------------- |---------------:|--------------:|--------------:|----------:|--------:|-----:|---------:|------:|------:|----------:| | UseStruct | 0.0000 ns | 0.0000 ns | 0.0000 ns | 0.000 | 0.00 | 1 | - | - | - | - | | UseClass | 8.1425 ns | 0.1873 ns | 0.1839 ns | 1.000 | 0.00 | 2 | 0.0127 | - | - | 40 B | | Use100Struct | 36.9359 ns | 0.4026 ns | 0.3569 ns | 4.548 | 0.12 | 3 | - | - | - | - | | Use100Class | 759.3495 ns | 14.8029 ns | 17.0471 ns | 93.144 | 3.24 | 4 | 1.2751 | - | - | 4000 B | | Use10000Struct | 3,002.1976 ns | 25.4853 ns | 22.5920 ns | 369.664 | 8.91 | 5 | - | - | - | - | | Use10000Class | 76,529.2751 ns | 1,570.9425 ns | 2,667.5795 ns | 9,440.182 | 346.76 | 6 | 127.4414 | - | - | 400000 B |
✔️如果该类型的实例很小且通常存在时间很短,或者通常嵌入在其他对象中,请考虑定义一个结构而不是类。