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

时间:2009-12-23 07:25:38

标签: c# class struct

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

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

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

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

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

9 个答案:

答案 0 :(得分:52)

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

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

答案 1 :(得分:10)

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

GetHashcode和Equals的结构实现基于结构实例的二进制内容+引用成员的反射(如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

问候,最佳编码

答案 2 :(得分:7)

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

答案 3 :(得分:5)

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

因此。除非你知道自己在做什么,否则只需坚持上课。

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

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

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

答案 4 :(得分:4)

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

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

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

[employeeInfoStruct is a struct containing the following field]
public Decimal YearlyBonus;

[someEmployeeContainer is an instance of a class which includes the following method]
EmployeeInfoStruct GetEmployeeInfo(String id);  // Just the signature--code is immaterial

[some other method uses the following code]
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参数传递。尽管内置集合确实不能很好地促进这种使用,但它可以使用比使用类更便宜的数百字节结构。

答案 5 :(得分:3)

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

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

答案 6 :(得分:2)

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

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

答案 7 :(得分:2)

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

答案 8 :(得分:0)

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

您可以在此处找到基准:http://00sharp.wordpress.com/2013/07/03/a-case-for-the-struct/