MSDN说,当您需要轻量级对象时,应该使用结构。在某个结构比一类更可取的情况下,还有其他情况吗?
有些人可能忘记了:
- 结构可以有方法。
- 结构不能被继承。
我了解结构和类之间的技术差异,只是对何时使用结构感觉不太好。
MSDN说,当您需要轻量级对象时,应该使用结构。在某个结构比一类更可取的情况下,还有其他情况吗?
有些人可能忘记了:
我了解结构和类之间的技术差异,只是对何时使用结构感觉不太好。
Answers:
MSDN的答案是: 在类和结构之间进行选择。
基本上,该页面为您提供了4个项目的清单,并说要使用一个类,除非您的类型满足所有条件。
除非类型具有以下所有特征,否则请不要定义结构:
- 它在逻辑上表示一个值,类似于基本类型(整数,双精度型,等等)。
- 它的实例大小小于16个字节。
- 这是一成不变的。
- 不必经常装箱。
ref
合理地将它们作为参数传递。将具有4,000个字段的结构作为ref参数传递给一个方法,将其更改会比将具有4个字段的按值传递给返回修改版本的方法便宜。
令我惊讶的是,我没有阅读任何先前的答案,我认为这是最关键的方面:
我想要没有身份的类型时使用结构。例如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);
}
}
如果您有此结构的两个实例,则不必关心它们是内存中的单个数据还是两个。您只关心它们所拥有的价值。
return false
是应该在那里的,现在进行纠正。
比尔·瓦格纳(Bill Wagner)在他的“有效的c#”一书中有一章对此进行了介绍(http://www.amazon.com/Effective-Specific-Ways-Improve-Your/dp/0321245660)。他总结使用以下原则:
- 类型数据存储的主要责任是什么?
- 它的公共接口是否完全由访问或修改其数据成员的属性定义?
- 您确定您的类型永远不会有子类吗?
- 您确定您的类型永远不会被多态处理吗?
如果您对所有4个问题回答“是”:请使用结构。否则,使用一个类。
在以下情况下使用课程:
在以下情况下使用结构:
当我想将一些值组合在一起以将方法调用中的内容传递回去时,我一直使用结构,但是在读取这些值之后,就不需要将其用于任何东西。只是保持物品清洁的一种方式。我倾向于将结构中的事物视为“可丢弃”,而将类中的事物视为更为有用和“实用”
如果一个实体将是不可变的,那么使用结构还是类的问题通常是性能而非语义之一。在32/64位系统上,无论类中的信息量如何,类引用都需要4/8字节来存储。复制一个类引用将需要复制4/8字节。另一方面,每个不同类实例除了拥有的信息和对其引用的内存开销外,还将有8/16字节的开销。假设有人想要一个由500个实体组成的数组,每个实体包含四个32位整数。如果实体是结构类型,则该数组将需要8,000个字节,而不管所有500个实体都是相同,全部不同还是介于两者之间。如果实体是类类型,则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的怪癖会导致一个人的应用程序中的值类型语义被破坏,请使用一类,然后喃喃自语。
此外,上述出色的答案:
结构是值类型。
永远不能将它们设置为Nothing。
设置结构= Nothing,会将其所有值类型设置为其默认值。
当您实际上不需要行为,但是需要比简单数组或字典更多的结构。
后续操作 这就是我通常对结构的看法。我知道他们可以有方法,但是我喜欢保持整体的精神差异。
这是一个古老的话题,但希望提供一个简单的基准测试。
我创建了两个.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 |
嗯...
我不会使用垃圾回收作为/反对使用struct vs类的参数。托管堆的工作原理类似于堆栈-创建对象只是将其放在堆的顶部,这几乎与在堆栈上分配的速度一样快。此外,如果对象是短暂的并且无法在GC周期中幸存,则释放是免费的,因为GC仅与仍可访问的内存一起使用。(搜索MSDN,其中有一系列有关.NET内存管理的文章,我太懒了以至于无法对其进行深入研究)。
在大多数情况下,我使用struct时都会竭尽全力,因为后来我发现拥有引用语义会使事情变得更简单。
无论如何,上面发布的MSDN文章中的那四点似乎是一个很好的指南。
class MutableHolder<T> { public T Value; MutableHolder(T value) {Value = value;} }
,然后a MutableHolder<T>
将是具有可变类语义的对象(如果T
是struct或不可变的类类型,则效果也很好)。
我认为最好的答案是,当您需要的是属性集合时使用struct,而当它是属性和行为的集合时使用class。