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

时间:2008-09-17 17:20:49

标签: .net oop

MSDN说你应该在需要轻量级对象时使用结构。当结构优于某个类时,还有其他任何情况吗?

有些人可能忘记了:

  1. 结构可以有方法。
  2. structs 无法继承。
  3. 我理解结构和类之间的技术差异,我对 使用结构时感觉不太好。

16 个答案:

答案 0 :(得分:289)

MSDN有答案: Choosing Between Classes and Structures

基本上,该页面为您提供了一个4项清单,并说除非您的类型符合所有条件,否则使用类。

  

除非是,否则不要定义结构   type具有以下所有内容   特性:

     
      
  • 它逻辑上表示单个值,类似于原始类型   (整数,双精度等)。
  •   
  • 实例大小小于16个字节。
  •   
  • 这是不可改变的。
  •   
  • 不必频繁装箱。
  •   

答案 1 :(得分:53)

我很惊讶我之前没有读过任何一个答案,我认为这是最重要的方面:

当我想要一个没有身份的类型时,我使用结构。例如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);
    }
}

如果你有这个结构的两个实例,你不关心它们是内存中的单个数据还是两个。你只关心他们持有的价值。

答案 2 :(得分:27)

比尔瓦格纳在他的“有效的c#”(http://www.amazon.com/Effective-Specific-Ways-Improve-Your/dp/0321245660)一书中有一章。他最后使用了以下原则:

  
      
  1. 类型数据存储的主要责任是什么?
  2.   
  3. 其公共接口是否完全由访问或修改其数据成员的属性定义?
  4.   
  5. 您确定您的类型永远不会有子类吗?
  6.   
  7. 你确定你的类型永远不会被多态化处理吗?
  8.         

    如果对所有4个问题回答“是”:使用结构。否则,请使用   类。

答案 3 :(得分:15)

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

另见前面的问题,例如

What's the difference between struct and class in .NET?

答案 4 :(得分:11)

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

  1. 一个对象应该是只读的(每次传递/分配它被复制的结构)。在多线程处理方面,只读对象很棒,因为在大多数情况下它们不需要锁定。

  2. 一个物体很小而且生命短暂。在这种情况下,很有可能在堆栈上分配对象,这比将其放在托管堆上要有效得多。一旦超出其范围,对象分配的内存将被释放。换句话说,垃圾收集器的工作量较少,内存使用效率更高。

答案 5 :(得分:9)

在以下情况下使用课程:

  • 它的身份很重要。在通过值传递给方法时,结构会隐式复制。
  • 内存占用量很大。
  • 其字段需要初始化程序。
  • 您需要从基类继承。
  • 您需要多态行为;

在以下情况下使用结构:

  • 它将像一个原始类型(int,long,byte等)。
  • 内存占用量必须很小。
  • 您正在调用P / Invoke方法,该方法需要传入结构 值。
  • 您需要减少垃圾收集对应用程序性能的影响。
  • 其字段只需初始化为默认值。对于数字类型,此值为零,对于布尔类型,该值为false,对于引用类型,该值为null。
    • 请注意,在C#6.0中,结构体可以有一个可用于初始化的默认构造函数 struct的字段为非默认值。
  • 您不需要从基类继承(ValueType除外) 所有结构继承)。
  • 您不需要多态行为。

答案 6 :(得分:5)

当我想将一些值组合在一起以便从方法调用中返回时,我总是使用一个结构,但在读完这些值后我不需要使用它。只是一种保持清洁的方法。我倾向于将结构中的东西视为“一次性”而类中的东西更有用且“功能性”

答案 7 :(得分:4)

如果一个实体将是不可变的,那么使用结构或类的问题通常是性能而不是语义。在32/64位系统上,无论类中的信息量如何,类引用都需要存储4/8个字节;复制类引用将需要复制4/8字节。另一方面,除了它所拥有的信息和对它的引用的内存成本之外,每个 distinct 类实例将具有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的怪癖会导致在一个应用程序中导致破坏的值类型语义,使用一个类并嘟。。

答案 8 :(得分:3)

此外,上面的优秀答案:

结构是价值类型。

永远不能将它们设置为 Nothing

设置结构=无,将其所有值类型设置为其默认值。

答案 9 :(得分:2)

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

跟进 这就是我对结构的总体看法。我知道他们可以有方法,但我喜欢保持整体心理上的区别。

答案 10 :(得分:2)

正如@Simon所说,结构提供了“值类型”语义,因此如果您需要与内置数据类型类似的行为,请使用结构。由于结构是通过副本传递的,因此您需要确保它们的大小很小,大约16个字节。

答案 11 :(得分:2)

这是一个古老的话题,但是想要提供一个简单的基准测试。

我创建了两个.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; }
}

运行基准测试

  • 创建1个TestClass
  • 创建1个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 |

答案 12 :(得分:1)

嗯...

我不会使用垃圾收集作为/反对使用结构与类的参数。托管堆的工作方式与堆栈非常相似 - 创建一个对象只需将其放在堆的顶部,这几乎与在堆栈上分配一样快。此外,如果一个对象是短暂的并且不能在GC循环中存活,则释放是免费的,因为GC仅适用于仍可访问的内存。 (搜索MSDN,有一系列关于.NET内存管理的文章,我只是懒得去挖掘它们。)

大多数时候我使用结构,我最终会这样做,因为我后来发现使用引用语义会使事情变得更简单。

无论如何,上面发布的MSDN文章中的这四点似乎是一个很好的指导原则。

答案 13 :(得分:1)

结构在堆栈而不是堆上,因此它们是线程安全的,并且应该在实现传输对象模式时使用,你永远不想在堆上使用它们是易失性的,你想在这种情况下使用调用堆栈,这是使用结构的一个基本案例,我对这里的所有答案感到惊讶,

答案 14 :(得分:0)

✔️ 如果类型的实例很小且通常寿命很短,或者通常嵌入到其他对象中,请考虑定义结构而不是类。

答案 15 :(得分:-2)

我认为最好的答案就是当你需要的是一个属性集合时使用struct,当它是属性和行为的集合时就是类。