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

Est*_*aya 295 .net oop

MSDN说你应该在需要轻量级对象时使用结构.当结构比类更可取时,还有其他任何情况吗?

有些人可能忘记了:

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

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

Owe*_*enP 293

MSDN的答案是: 在类和结构之间进行选择.

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

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

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

  • 我想这是另一种说"它有价值语义"的方式 (9认同)
  • @Chuu:在设计JIT编译器时,Microsoft决定优化复制16字节或更小字节的代码; 这意味着复制一个17字节的结构可能比复制一个16字节的结构要慢得多.我认为没有特别的理由期望微软将这种优化扩展到更大的结构,但重要的是要注意虽然17字节结构的复制速度可能比16字节结构要慢,但在很多情况下,大结构可能比大型类对象,结构*的相对优势*随结构的大小增长*. (4认同)
  • @Chuu:将相同的使用模式应用于大型结构,因为类很容易导致代码效率低下,但正确的解决方案通常不是用类替换结构,而是更有效地使用结构; 最值得注意的是,应该避免通过值传递或返回结构.只要合理的话,将它们作为`ref`参数传递.将具有4,000个字段的结构作为ref参数传递给方法,该方法更改一个将比将具有4个字段值的结构传递给返回修改版本的方法更便宜. (3认同)
  • 他们可能推荐这个,因为如果结构是不可变的,那么它具有值语义而不是引用语义并不重要.只有在制作副本后改变对象/结构时,区别才有意义. (2认同)

And*_*nea 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);
    }
}
Run Code Online (Sandbox Code Playgroud)

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

  • 有点偏离主题,但为什么当obj不是ThreeDimensionalPoint时抛出ArgumentException?在这种情况下,你不应该只返回假吗? (3认同)
  • 这是正确的,我过于渴望.`return false`应该是那里的,现在纠正. (3认同)

Bar*_*ens 27

Bill Wagner在他的"有效的c#"(http://www.amazon.com/Effective-Specific-Ways-Improve-Your/dp/0321245660)一书中有一章.他最后使用以下原则:

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

如果对所有4个问题回答"是":使用结构.否则,请使用课程.

  • 那么...数据传输对象(DTO)应该是结构吗? (2认同)
  • @cruizer取决于你的情况.在一个项目中,我们在DTO中有共同的审计领域,因此编写了一个其他人继承的基础DTO. (2认同)

Sim*_*ele 15

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

另见前面的问题,例如

.NET中struct和class之间有什么区别?


Paw*_*ich 11

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

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

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


Yas*_*ata 10

使用类如果:

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

使用结构如果:

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


Rya*_*rin 5

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


sup*_*cat 5

如果实体将是不可变的,那么是使用结构体还是类的问题通常是性能问题而不是语义问题。在 32/64 位系统上,类引用需要 4/8 字节来存储,而不管类中的信息量如何;复制类引用将需要复制 4/8 个字节。另一方面,每一个不同的除了它保存的信息和引用它的内存成本之外,类实例还将有 8/16 字节的开销。假设一个人想要一个包含 500 个实体的数组,每个实体包含四个 32 位整数。如果实体是结构类型,则无论 500 个实体是否全部相同、全部不同或介于两者之间,数组都将需要 8,000 个字节。如果实体是类类型,则包含 500 个引用的数组将占用 4,000 个字节。如果这些引用都指向不同的对象,则每个对象需要额外的 24 个字节(所有 500 个需要 12,000 个字节),总共 16,000 个字节——是结构类型存储成本的两倍。另一方面,在创建一个对象实例然后复制对所有 500 个数组槽的引用的代码中,该实例的总成本为 24 个字节,4 个字节,000 表示数组——总共 4,024 个字节。一大笔节省。很少有情况会像最后一种情况一样有效,但在某些情况下,可以将一些引用复制到足够的数组槽中,以使这种共享变得有价值。

如果实体应该是可变的,那么使用类还是结构的问题在某些方面更容易。假设“事物”是一个结构体或类,它有一个名为 x 的整数字段,并且执行以下代码:

  事物 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 的怪癖会导致在一个应用程序中破坏值类型语义,请使用类并咕哝。


小智 5

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

我创建了两个 .cs 文件:

public class TestClass
{
    public long ID { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

public struct TestStruct
{
    public long ID { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

运行基准测试:

  • 创建 1 个测试类
  • 创建 1 个测试结构
  • 创建 100 个测试类
  • 创建 100 个测试结构
  • 创建 10000 个测试类
  • 创建 10000 个测试结构

结果:

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 |
Run Code Online (Sandbox Code Playgroud)