哪个最适合数据存储结构/类?

我们已经在SO中看到了很多关于c#中的类vs结构的讨论。 最后得出的结论是它的堆/堆内存分配。 并建议在小型数据结构中使用结构

现在我有一种情况来决定这两种选择中的简单数据存储。 在我们的应用程序中,我们有数千个类,只是作为简单的数据存储(仅暴露的公共字段),它们在不同的模块和服务之间传递。

根据我的理解,出于性能原因,我觉得最好继续使用struct而不是类。 因为这些是简单的数据结构,所以只能充当数据存储。

在继续这个之前,我需要经历过这场斗争的人们的一些专家建议。

  • 我的理解是正确的吗?
  • 我见过大多数ORM都有类作为数据存储。 所以我怀疑是否应该继续使用类而不是结构。 那会是什么?

我会根据以下标准做出选择

  • 引用类型与值类型语义。 如果2个对象只是相同,如果它们是同一个对象,则表示引用类型语义=> class。 如果其成员的值定义相等(例如,如果两个DateTimes相等,如果它们都表示相同的时间点,即使它们是2个不同的对象),则值类型语义=> struct
  • 对象的内存占用。 如果对象很大且频繁分配,那么使它成为一个结构会更快地消耗堆栈,因此我宁愿将它作为一个类。 相反,我宁愿避免对小值类型的GC惩罚; 因此使它们成为一个结构。
  • 你能让对象不可变吗? 我发现结构非常适合“价值对象” – 来自DDD书籍。
  • 根据这个对象的用法,你会面临一些装箱拆箱的惩罚吗? 如果是的话,去上课。

Structs over Classes的一个非常酷但不太知名的优点是在结构中有一个GetHashcode和Equals的自动实现。
当字典需要键时,这非常有用

GetHashcode和Equals的结构实现基于结构实例的二进制内容+引用成员的reflection(如String成员和类的其他实例)

因此以下代码适用于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

问候,最好的编码

结构应该定义为不可变的,而不应该在类中。 如果您认为您的对象将变得小而且不可变,您可以继续制作它们的结构,或者让它们成为类。

我似乎永远不会真的记得,结构是如何不同的,但它们是。 以微妙的方式。 事实上,有时他们会来咬你。

所以。 除非你知道自己在做什么,否则只要坚持上课。

我知道这听起来有点新手。 我知道我现在应该去查看差异并在这里显示它们 – 但这已经由其他人完成了。 我要说的是,添加不同类型的对象会产生语义负担,一些额外的复杂性,您需要仔细考虑。

如果我没记错的话,最大的问题之一是结构的值语义:传递它们将导致不同的对象(因为它们通过值传递)。 如果你在一个地方改变一些字段,请注意在所有其他地方字段没有改变! 这就是为什么每个人都建议结构的不变性!

编辑:对于您描述的情况,结构将不起作用

类对象的优点是可以传递对它的引用,如果它到达外部代码,则这种引用的范围和生命周期是无限的。 结构体的优点是虽然可以传递对它们的短命引用,但是不可能传递永久的混杂引用。 这有助于避免担心是否存在此类引用。

有些人建议,可变数据持有者不应该是结构化的。 我强烈反对。 在许多情况下,为了保存数据而存在的实体应该是结构, 特别是如果它们是可变的。 Eric Lippert多次发布他认为可变值类型为邪恶(在标签“mutable”和“struct”下搜索)。 毫无疑问,.net允许使用可变结构来完成某些事情,它不应该,并且不方便地允许它应该的某些事情,但POD(“普通旧数据”)结构没有变异方法,但是通过公共字段公开其整个状态,在其行为中具有非常有用的一致性,而不与任何其他数据类型共享。 使用POD结构可能会使不熟悉它们如何工作的人感到困惑,但会使任何人都能更好地阅读该程序。

例如,考虑以下代码,假设EmployeeInfoStruct只包含值类型和不可变类类型,如String:

 [employeeInfoStruct是包含以下字段的结构]
 public Decimal YearlyBonus;

 [someEmployeeContainer是包含以下方法的类的实例]
 EmployeeInfoStruct GetEmployeeInfo(String id);  //只是签名 - 代码无关紧要

 [其他一些方法使用以下代码]
 EmployeeInfoStruct anEmployee = someEmployeeContainer.GetEmployeeInfo(“123-45-6789”);
 anEmployee.YearlyBonus + = 100;

Eric Lippert抱怨上述代码将改变anEmployee中的值,但该更改不会对容器产生任何影响。 我建议这是一件好事 – 任何知道结构如何工作的人都可以查看上面的代码并知道对结构变量的写入将影响该变量,但除非程序稍后使用其他方法,否则不会影响任何其他方法(也许SetEmployeeInfo)将该变量存储在某个地方。

现在将EmployeeInfoStruct替换为EmployeeInfoClass,它具有类型为YearlyBonus的读/写属性。 仅使用上面的信息,可以说一下对someEmployeeContainer和anEmployee之间的关系有什么看法? 根据anEmployee类的实现(除非EmployeeInfoClass被密封,实际上可能或可能不是EmployeeInfoClass)和someEmployeeContainer,对象之间的关系可以是任何东西。 写给一个人可能:

  1. 对另一方没有影响
  2. 以“自然”方式更新另一个
  3. 以任意方式腐蚀对方

对于只包含值类型或不可变类的字段的结构,语义总是#1。 一个人不必查看结构本身的代码,也不必查看容器的代码。 相比之下,如果anEmployee.Salary或someEmployeeContainer.GetEmployee是虚拟的,那么就不可能真正知道语义是什么。

重要的是要注意,如果结构很大,按值传递或从函数返回它们可能很昂贵。 通常,在可能的情况下将大型结构作为ref参数传递会更好。 虽然内置集合确实不能很好地促进这种使用,但它可以使用比使用类更便宜的数百字节结构。

关于结构不可变的注释是正确的。 这就是它可以咬你的地方。 您可以使用字段设置器定义结构,但是当您更改字段值时,会创建一个新实例。 因此,如果您持有对旧对象的引用,它仍将引用旧值。 我不喜欢使用可变结果因为这会产生细微而复杂的错误(特别是如果你使用复杂的复合语句)。

另一方面,使用具有不可变状态的类也有很多很好的理由(想想字符串)。

我记得在MSDN上给出的一个建议是struct不应该大于16或21个字节。 寻找链接,但还没找到它。

主要的含义是,一旦你的数据类型中有一个字符串 – 让它成为一个没有思考的类。 否则结构不应该持有太多。

我认为你有正确的想法。 结构用于模拟数据类型。 它们是价值驱动而非基于参考。 如果您查看大多数基本数据类(int,double,decimal,等等)的MSDN文档,它们都基于结构。 尽管如此,结构不应该因为同样的原因而被滥用。 存储该结构中的所有内容的空间在实例化后立即分配,其中类只为内部的所有内容分配空间。 如果数据是足够小的块,这不是一个问题,而结构是要走的路。 如果这是一个问题,请选择课程。 如果你不知道,最好坚持你熟悉的东西。

如果你有低延迟要求和很多对象慢速垃圾收集可能是一个问题。 在这种情况下,struct非常有用,因为如果值类型不包含任何引用类型,则垃圾收集器不需要扫描值类型的层次结构。

你可以在这里找到一个基准:http: //00sharp.wordpress.com/2013/07/03/a-case-for-the-struct/