什么时候应该使用结构体而不是类?

MSDN说当需要轻量级对象时应该使用结构。在其他情况下,结构体比类更可取吗?

有些人可能已经忘记了:

  1. 结构体可以有方法。
  2. 结构体不能被继承。

我理解结构体和类之间的技术差异,我只是对使用结构体没有很好的感觉。

108343 次浏览

当您需要值类型语义而不是引用类型时,请使用结构。结构体是按值复制的,所以要小心!

另见以前的问题,例如:

.NET中struct和class的区别是什么?< / >

当您不需要行为,但需要比简单的数组或字典更多的结构时。

< >强跟进 这就是我对结构体的一般看法。我知道他们可以有自己的方法,但我喜欢保持精神上的整体区分

正如@Simon所说,结构体提供了“值类型”语义,所以如果你需要类似于内置数据类型的行为,可以使用结构体。由于结构体是通过复制传递的,所以要确保它们的大小很小,大约16字节。

我会在以下情况下使用结构体:

  1. 对象应该是只读的(每次传递/赋值一个结构体都会被复制)。当涉及到多线程处理时,只读对象是很棒的,因为它们在大多数情况下不需要锁定。

  2. 物体很小,寿命很短。在这种情况下,很有可能将对象分配到堆栈上,这比将其分配到托管堆上要有效得多。更重要的是,由对象分配的内存将被释放,只要它超出其作用域。换句话说,垃圾收集器的工作量更少,内存的使用效率更高。

MSDN有答案: 在类和结构之间选择 . < / p >

基本上,这个页面给你一个4项清单,并告诉你使用一个类,除非你的类型满足所有的标准。

不定义结构,除非 类型包含以下所有内容 特点:< / p >

  • 逻辑上表示一个值,类似于基本类型 (integer, double,等等).
  • 实例大小小于16字节。
  • 它是不可变的。
  • 它不需要经常装箱。

当我想要将几个值组合在一起以从方法调用中传递内容时,我总是使用结构体,但在读取这些值之后,我将不需要将它用于任何事情。只是为了保持干净。我倾向于将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)中有一章关于这一点。他总结了以下原则:

  1. 类型数据存储的主要职责是什么?
  2. 它的公共接口是否完全由访问或修改其数据成员的属性定义?
  3. 你确定你的类型永远不会有子类吗?
  4. 您确定您的类型永远不会被多态处理吗?
如果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:

  • 它的作用类似于基本类型(int、long、byte等)。
  • 它必须有一个小的内存占用。
  • 你正在调用一个P/Invoke方法,该方法需要传递一个结构 李价值。< / >
  • 您需要减少垃圾收集对应用程序性能的影响。
  • 它的字段只需要初始化为默认值。对于数字类型,此值为零,对于布尔类型为假,对于引用类型为空。
      注意,在c# 6.0中,struct可以有一个默认构造函数,用于初始化 . struct的字段为非默认值 李< / ul > < / >
    • 你不需要从基类继承(除了ValueType,从它继承
    • 您不需要多态行为。

我认为最好的答案就是当你需要的是属性的集合时使用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; }
}

运行基准:

  • 创建一个TestClass
  • 创建一个TestStruct
  • 创建100个TestClass
  • 创建100个TestStruct
  • 创建10000个TestClass
  • 创建10000 TestStruct

结果:

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 |

✔️如果该类型的实例很小且通常存在时间很短,或者通常嵌入在其他对象中,请考虑定义一个结构而不是类。