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

有些人可能已经忘记了:

结构可以有方法。 结构不能被继承。

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


当前回答

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

我已经创建了两个。cs文件:

public class TestClass
{
    public long ID { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
}

and

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 |

其他回答

我很惊讶我没有读到之前的答案,我认为这是最关键的方面:

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

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

If an entity is going to be immutable, the question of whether to use a struct or a class will generally be one of performance rather than semantics. On a 32/64-bit system, class references require 4/8 bytes to store, regardless of the amount of information in the class; copying a class reference will require copying 4/8 bytes. On the other hand, every distinct class instance will have 8/16 bytes of overhead in addition to the information it holds and the memory cost of the references to it. Suppose one wants an array of 500 entities, each holding four 32-bit integers. If the entity is a structure type, the array will require 8,000 bytes regardless of whether all 500 entities are all identical, all different, or somewhere between. If the entity is a class type, the array of 500 references will take 4,000 bytes. If those references all point to different objects, the objects would require an additional 24 bytes each (12,000 bytes for all 500), a total of 16,000 bytes--twice the storage cost of a struct type. On the other hand, of the code created one object instance and then copied a reference to all 500 array slots, the total cost would be 24 bytes for that instance and 4,000 for the array--a total of 4,024 bytes. A major savings. Few situations would work out as well as the last one, but in some cases it may be possible to copy some references to enough array slots to make such sharing worthwhile.

如果实体应该是可变的,那么使用类还是结构的问题在某些方面会更容易。假设“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。

Mutable structures and mutable classes have fundamentally different behaviors, though .net has some quirks in its handling of struct mutations. If one wants value-type behavior (meaning that "t2=t1" will copy the data from t1 to t2 while leaving t1 and t2 as distinct instances), and if one can live with the quirks in .net's handling of value types, use a structure. If one wants value-type semantics but .net's quirks would cause lead to broken value-type semantics in one's application, use a class and mumble.

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

我已经创建了两个。cs文件:

public class TestClass
{
    public long ID { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
}

and

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 |

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

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

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

an object is supposed to be read only(every time you pass/assign a struct it gets copied). Read only objects are great when it comes to multithreaded processing as they don't requite locking in most cases. an object is small and short-living. In such a case there is a good chance that the object will be allocated on the stack which is much more efficient than putting it on the managed heap. What is more the memory allocated by the object will be freed as soon as it goes outside its scope. In other words it's less work for Garbage Collector and the memory is used more efficient.