现在我面临一个选择简单数据存储的情况。目前在我们的应用程序中有成千上万个类,只充当简单的数据存储(仅公开暴露字段)并在不同的模块和服务之间传递。
根据我的理解,我觉得出于性能原因,与其使用类而不是结构体更好。因为它们只是充当数据存储的简单数据结构。
在继续之前,我需要一些有经验的人的专业建议。
你的理解正确吗?
我已经看到大多数ORM都将类作为数据存储方式。所以我怀疑应该有理由使用类而不是结构体。那会是什么呢?
根据以下标准进行选择:
结构体相对于类的一个不太为人所知的优势是,在结构体中有GetHashCode和Equals的自动实现。这在需要字典键时非常有用。
结构体实现GetHashCode和Equals基于结构体实例的二进制内容+反射引用成员(如字符串成员和其他类的实例)
因此,以下代码适用于GethashCode / Equals:
public struct Person
{
public DateTime Birthday { get; set; }
public int Age{ get; set; }
public String Firstname { get; set; }
}
class Program
{
static void Main(string[] args)
{
Person p1 = new Person { Age = 44, Birthday = new DateTime(1971, 5, 24), Firstname = "Emmanuel" };
Person p2 = new Person { Age = 44, Birthday = new DateTime(1971, 5, 24), Firstname = "Emmanuel" };
Debug.Assert(p1.Equals(p2));
Debug.Assert(p1.GetHashCode() == p2.GetHashCode());
}
}
当Person是一个结构体时,两个断言都成功。 如果Person是类而不是结构体,则两个断言都失败。
参考: https://msdn.microsoft.com/en-Us/library/2dts52z7%28v=vs.110%29.aspx
祝好,愉快编程!
在定义类时不应该使用结构体,而应该定义为不可变的结构。如果你认为你的对象将是小而不可变的,那么可以将它们定义为结构体,否则请让它们成为类。
Rectangle
是一个可变的类而不是一个结构体。如果尝试执行MyControl.Bounds.Width += 20;
,那么应该发生什么是完全不清楚的。Bounds
是一个结构体而不是一个类的事实使得以上操作无效(甚至连编译都无法通过),我们必须将Bounds
复制到一个临时的Rectangle
中进行修改,然后使用新的矩形设置bounds,或者找到其他方式... - supercatBounds
中任意组合字段的方法,但必须搜索Control
的定义;相比之下,将Bounds
复制到临时Rectangle
中,修改它,然后将其设置回来,正如我们只需要知道Rectangle
是一个具有可变字段的结构体,而Bounds
是类型为Rectangle
的读写属性一样,这种方法完全符合预期。无需进一步的文档说明。) - supercat我似乎永远也记不清楚,结构体(struct)和类(class)具体有什么不同,但它们确实存在微妙的差别。事实上,有时候它们会悄悄地给你带来问题。
所以,除非你知道自己在干什么,否则就坚持使用类吧。
我知道这听起来有点新手,我知道我现在应该去查一下它们之间的区别并在这里展示它们——但这已经被其他人做过了。我想说的是,添加不同类型的对象会增加语义负担,增加一些额外的复杂性,你应该谨慎考虑。
如果我没记错的话,其中一个最大的问题是结构体的值语义:传递它们将导致创建不同的对象(因为它们是按值传递的)。如果你在某个地方更改了某个字段,请注意在所有其他地方该字段没有被更改!这就是为什么每个人都建议对结构体进行不可变性处理!
编辑:对于您描述的情况,结构体将不起作用!
P
和 Q
是类型为 Point
的变量(它是一个带有字段 X
和 Y
的结构体),那么 P=Q;
就相当于 P.X=Q.X; P.Y=Q.Y;
。请注意,复制结构体将复制所有字段,包括公共和私有字段。如果你从这个角度来看待事物,就会明显地发现结构体的行为与类不同。把结构体和类视为“几乎相同”是不明智的。.net 隐式地为每个结构体定义了一个同名的类,该类具有相同的字段、属性和方法,这可能会让结构体看起来像类... - supercatPoint
强制转换为Object
,它将被转换为类型为Point
的类对象。如果您将对象转换回Point
并将其存储在该类型的变量中,则该对象的字段X
和Y
将被复制到Point
变量中。 - supercat对于只包含值类型或不可变类字段的结构体,其语义始终是#1。无需查看结构体本身的代码或容器的代码即可知道。相比之下,如果anEmployee.Salary或someEmployeeContainer.GetEmployee是虚拟的,则真正知道语义将是不可能的。
需要注意的是,如果结构体很大,则按值传递或从函数返回它们可能很昂贵。通常最好在可能的情况下将大型结构体作为ref参数传递。虽然内置集合并没有很好地促进这种使用方式,但它可以使使用数百字节的结构体比使用类更便宜。
我记得在MSDN上有一条建议,即结构体的大小不应超过16或21个字节。我正在寻找链接,但尚未找到。
主要含义是,一旦在您的数据类型中有一个字符串,请将其视为类而不是结构体。否则,结构体不应该包含太多内容。
我认为你想法正确。结构体的作用是模拟数据类型。它们是基于值而非基于引用的。如果你查看大多数基本数据类(如int、double、decimal等)的MSDN文档,它们都是基于结构体的。然而,正因为如此,结构体不应过度使用。一旦实例化结构体,它就会分配所有内容的存储空间,而类只会分配指向内部所有内容的引用的存储空间。如果数据的大小足够小,这不是问题,那么结构体是最好的选择。如果这是一个问题,请使用类。如果你不确定,最好还是坚持你熟悉的。