我应该使用struct还是class?

时间:2010-10-06 13:15:16

标签: c# class-design boxing

我处于经典的设计困境。我正在编写一个C#数据结构,用于包含值和度量单位元组(例如7.0毫米),我想知道是否应该使用引用类型或值类型。

struct 的好处应该是减少堆操作,从而使我在表达式中获得更好的性能并减少对垃圾收集器的压力。对于像这样的简单类型,这通常是我的选择,但在这个具体案例中存在缺点。

元组是一个相当通用的分析结果框架的一部分,其中结果在WPF应用程序中以不同的方式呈现,具体取决于结果值的类型。 WPF及其所有数据模板,值转换和模板选择器都可以很好地处理这种弱类型。这意味着如果我的元组表示为结构,则该值将经历大量装箱/拆箱。实际上,在表达式中使用元组对于在拳击场景中的使用来说将是次要的。为了避免所有拳击,我考虑将我的类型声明为一个类。关于结构的另一个担心是在WPF中可能存在双向绑定的缺陷,因为在代码中的某个地方而不是引用副本会更容易得到元组的副本。

我也有一些方便的运算符重载。我可以使用重载的比较运算符比较毫米和毫米毫米没有问题。但是我不喜欢重载==和!=的想法,如果我的元组是一个类,因为约定是==和!=是引用类型的ReferenceEquals(与System.String不同,这是另一个经典的讨论)。如果==和!=重载,有人会写 if(myValue == null)并在myValue有一天结果为空时得到一个讨厌的运行时异常。

另一个方面是,在C#(与例如C ++不同)中没有明确的方法来区分代码用法中的引用和值类型,但语义却非常不同。我担心我的元组的用户(如果声明了struct)假定该类型是一个类,因为大多数自定义数据结构都是并且假设是引用语义。这是另一个争论,为什么人们应该更喜欢类,因为那是用户所期望的,而且没有“。” /“ - >”告诉他们分开。一般来说,我几乎总是会使用一个类,除非我的探查器告诉我使用一个结构,只是因为类语义最有可能被其他程序员所期望,而C#只有模糊的提示,无论它是一个还是另一个。

所以我的问题是:

在决定是否值得参考或参考时,我应该考虑哪些其他因素?

可以在任何的情况下在一个类中进行== /!=重载是否合理?

程序员假设的东西。大多数人可能会认为称为“点”的东西是值类型。如果您阅读一些带有“UnitValue”的代码,您会假设什么?

根据我的使用说明,您会选择什么?

6 个答案:

答案 0 :(得分:9)

  

结构的好处应该是减少堆操作,使我在表达式中获得更好的性能并减少对垃圾收集器的压力

在没有任何背景的情况下,这是一个巨大而危险的过度概括。结构自动符合堆栈条件。如果(并且仅当)它的生命周期和暴露不会扩展到声明它的函数之外,结构可以放在堆栈上,它不会在该函数中被包装,并且可能还有许多其他标准没有立即浮现在脑海中。这意味着将其作为lambda表达式或委托的一部分意味着它无论如何都将存储在堆上。 重点是不要担心,因为你的瓶颈在其他地方有99.9%的可能性

对于运算符重载,没有什么能阻止你(在技术上或哲学上)重载你的类型上的运算符。虽然你在技术上是正确的,但是默认情况下,引用类型之间的相等比较在语义上等同于object.ReferenceEquals,这不是一个全部和最终的规则。关于运算符重载,有两个基本要注意事项:

1。)(从实际角度来看,这可能是最重要的)操作符多态。也就是说,您将只使用类型上定义的运算符,因为它们被引用,而不是它们实际存在。

例如,如果我声明一个类型Foo来定义一个总是返回true的重载的equals运算符,那么我这样做:

Foo foo1 = new Foo();
Foo foo2 = new Foo();
object obj1 = foo1;

bool compare1 = foo1 == foo2; // true
bool compare2 = foo1 == obj1; // false

尽管obj1实际上是Foo的实例,但重载的运算符不存在于我引用{{1}中存储的实例的类型层次结构级别引用,所以它回退到参考比较。

2.)比较操作应该是确定性的。不应该使用重载运算符比较相同的两个实例,并且能够产生不同的结果。 实际上,这种要求通常会导致类型是不可变的(因为能够告诉类中的一个或多个值之间的区别但是从等于运算符获取obj1是相当违反直觉的),但从根本上说它只是意味着你不应该改变一个将改变比较操作结果的实例中的状态值。如果在你的场景中能够改变实例状态信息的某些而不影响比较结果,那么你没有理由不这样做。这只是一个罕见的案例。

答案 1 :(得分:1)

  

但是,如果我的元组是一个类,我不喜欢重载==和!=的想法,因为约定是==和!=是引用类型的ReferenceEquals

不,会议略有不同:

  

(与System.String不同,这是另一个经典讨论)。

不,这是同样的讨论。

关键不在于类型是否是引用类型。 - 这是类型的行为是否为值。这适用于String,对于您要关注operator ==!=的任何类都应如此。

在设计一个逻辑上是值的类型时,你应该注意一件事:使它成为不可变的(请参阅Stack Overflow上的其他讨论),并正确实现比较语义:

  

如果==和!=重载,有人会写if(myValue == null)并在myValue有一天结果为null时得到一个讨厌的运行时异常。

应该没有异常(毕竟,(string)null == null也不会产生异常!),这将是重载运算符实现中的一个错误。

答案 2 :(得分:0)

也许你可以从Eric Lippert那里得到this recent blog post的灵感。使用结构时要记住的最重要的事情是make them immutable。这是Jon Skeet的一个有趣的blog post,其中一个可变结构可能导致很难调试问题。

答案 3 :(得分:0)

我不确定在UI代码中装箱/取消装箱你的价值的性能损失应该是你的主要关注点。例如,与布局过程相比,此性能命中较小。

事实上,你可以用另一种方式表达你的问题:你想要你的类型是可变的还是不可变的?我认为不变性与你的规格是合乎逻辑的。这是一个值,你自己说,命名为UnitValue。作为开发人员,我会对UnitValue不是值感到惊讶;)=>使用不可变结构

此外,null对测量没有任何意义。平等和比较也应该遵循测量规则来实施。

不,我认为在你的案例中没有使用ref类型而不是值类型的相关理由。

答案 4 :(得分:0)

在我看来,您的设计需要为元组调用值类型语义。 < 7.0,mm>应始终等于< 7.0,mm>从程序员的角度来看。 < 7.0,mm>正是它的各个部分的总和,并没有自己的标识。其他一切我都会觉得很困惑。这种情况也意味着不变性。

现在,如果使用结构或类来实现它,则取决于性能以及是否必须为每个元组支持空值。如果你选择结构,如果你只需要在少数情况下支持null,你就可以使用Nullable。

另外,您不能为元组提供引用类型包装器,用于显示目的吗?我不熟悉WPF,但我想这会消除所有的拳击操作。

答案 5 :(得分:0)

  

包含值和测量单位元组的数据结构(例如7.0毫米)

听起来它有价值语义。该框架提供了一种用于创建具有值语义的类型的机制,即struct。使用它。

您在问题的下一段中所说的几乎所有内容,包括pro和con值类型都是基于它如何与运行时的实现细节进行交互来优化的问题。由于在这方面既有利弊,也没有明显的效率赢家。既然你没有真正尝试它就找不到明确的效率赢家,那么在这方面进行优化的任何尝试都显然为时过早。尽管我已经厌倦了关于过早优化被假装有人试图做出更快或更小的东西,但这确实适用于此。

但有一点不是优化:

  

如果我的元组是一个类,我不喜欢重载==和!=的想法,因为约定是==和!=是引用类型的ReferenceEquals

根本不是真的。 默认是==和!=处理引用相等,但这是因为它是唯一有意义的默认默认值,而不了解类的语义。 ==和!=当它符合类语义时应该重载,当引用相等是唯一关心的东西时,应该使用ReferenceEquals。

  

如果==和!=重载,有人会写if(myValue == null)并在myValue有一天结果为null时得到一个讨厌的运行时异常。

仅当==重载出现新手错误时。通常的做法是:

public static bool operator == (MyType x, MyType y)
{
  if(ReferenceEquals(x, null))
    return ReferenceEquls(y, null);
  if(ReferenceEquals(y, null))
    return false;
  return x.Equals(y);
}

当然,Equals重载也应检查参数为null,如果是,则返回false,对于直接调用它的人来说。当一个或两个值为null时,对默认==行为调用此行为甚至没有显着的性能影响,那么关注点是什么?

  

另一个方面是C#中没有明确的方法(与例如C ++不同)在代码使用中区分引用和值类型,但语义却截然不同。

不是真的。就相等性而言,默认语义是非常不同的,但是由于您正在描述某些意图具有值语义,因此倾向于将其作为值类型而不是类类型。除此之外,可用的语义大致相同。就拳击,参考共享和其他方式而言,这些机制可能有所不同,但这又会重新进行优化。

  

可以== /!=在类中重载在任何情况下都是合理的吗?

我宁愿问,不能超载==和!=这是合理的,这对于班级来说是明智的做法吗?

至于我作为程序员对“UnitValue”的假设,我可能会认为它是一个结构,因为听起来应该是这样。但实际上,我甚至不会这样认为,因为在我重要的事情之前,我大多不会关心它,因为它听起来也应该是不可变的,是一个简化的集合(mutable之间的语义差异)在实践中,引用类型和可变结构更大,但是这个结构是不可思议的不可变的。)