创建新对象还是重置每个属性?


29
 public class MyClass
    {
        public object Prop1 { get; set; }

        public object Prop2 { get; set; }

        public object Prop3 { get; set; }
    }

假设我有一个对象myObjectMyClass并且需要重置其属性,那么创建一个新对象或重新分配每个属性会更好吗?假设我对旧实例没有任何其他用途。

myObject = new MyClass();

要么

myObject.Prop1 = null;
myObject.Prop2 = null;
myObject.Prop3 = null;


14
没有上下文无法真正回答。
CodesInChaos

2
确实是@CodesInChaos。创建新对象可能不是更可取的特定示例:对象池化并尝试最小化用于处理GC的分配。
XNargaHuntress 2015年

@ XGundam05但是即使那样,OP中建议的这项技术也不太可能是处理该问题的适当方法
Ben Aaronson

2
Suppose I have an object myObject of MyClass and I need to reset its properties-如果需要重置对象的属性或对问题域建模很有用,为什么不reset()为MyClass 创建方法。请参阅下面的HarrisonPaine的答案
Brandin

Answers:


49

实例化一个新对象总是更好,那么您就有1个地方可以初始化属性(构造函数)并可以轻松地对其进行更新。

想象一下,您向类添加了一个新属性,而宁愿更新构造函数,也不想添加也重新初始化所有属性的新方法。

现在,在某些情况下,您可能想重用一个对象,在这种情况下,重新初始化属性非常昂贵,并且您想保留它。但是,这将更加专业,并且您将具有特殊的方法来重新初始化所有其他属性。即使在这种情况下,有时您仍想创建一个新对象。


6
可能的第三种选择:myObject = new MyClass(oldObject);。尽管这意味着在新实例中原始对象的精确副本。
AmazingDreams 2015年

我认为new MyClass(oldObject)对于初始化成本很高的情况,这是最好的解决方案。
rickcnagy 2015年

8
复制构造函数更具C ++风格。.NET似乎更喜欢Clone()方法,该方法返回一个完全相同的新实例。
埃里克(Eric)2015年

2
该构造函数除了调用公共的Initialize()方法外什么也做不了,因此您仍然可以在任意点调用该初始化点,以有效地创建“新”的新对象。我使用的库具有类似于IInitialize接口的对象库,这些对象可以在对象池中使用并可以重复使用以提高性能。
乍得·肖金斯

16

绝大多数情况下,您绝对应该创建一个新对象。重新分配所有属性的问题:

  • 需要所有属性的公共设置程序,这极大地限制了您可以提供的封装级别
  • 知道您对旧实例是否还有其他用途,意味着您需要了解所有使用旧实例的地方。所以如果A上课B都是通过class的实例传递的,C那么他们必须知道是否将它们传递给同一个实例,如果是,则另一个实例是否仍在使用它。这紧密地结合了原本没有理由的课程。
  • 导致重复的代码-如gbjbaanb所示,如果您向构造函数添加参数,则调用该构造函数的所有地方都将无法编译,因此不会丢失任何位置。如果仅添加公共属性,则必须确保手动查找和更新“重置”对象的每个位置。
  • 增加复杂性。假设您正在循环中创建和使用该类的实例。如果使用您的方法,那么您现在必须在循环的第一次或循环开始之前分别进行初始化。无论哪种方式,都必须编写其他代码来支持这两种初始化方式。
  • 意味着您的班级无法保护自己免受无效状态的侵害。想象一下,您编写了一个Fraction包含分子和分母的类,并想强制要求始终将其减少(即分子和分母的gcd为1)。如果要允许人们公开设置分子和分母,这是不可能做到的,因为他们可能会从无效状态过渡到从一个有效状态转换为另一个有效状态。例如1/2(有效)-> 2/2(无效)-> 2/3(有效)。
  • 根本不是您所用语言的惯用语言,从而增加了维护代码的人员的认知摩擦。

这些都是非常重要的问题。而您所付出的额外工作所获得的回报是……什么都没有。通常,创建对象实例的成本非常低,因此性能收益几乎总是可以忽略不计。

就像其他答案提到的那样,唯一的时间表现可能就是您需要关注的一个问题是,如果您的班级在建筑方面做了一些非常昂贵的工作。但是即使在这种情况下,要使此技术起作用,您仍需要能够将昂贵的部分与要重置的属性分离开,以便能够使用flyweight模式或类似的模式


顺便说一句,可以通过不使用setter而是使用setter来缓解上述某些问题。public Reset在类上使用与构造函数相同的参数方法。如果出于某种原因您确实想退出此重置路线,则可能是一种更好的方法。

但是,增加的复杂性和重复性以及上面未解决的要点仍然是一个非常有说服力的论点,反对这样做,尤其是在权衡不存在的利益时。


我认为,对于重置而不是创建新对象而言,最重要的用例是实际上是否要重置对象。IE浏览器 如果您希望其他指向该对象的类在重置后看到一个新对象。
塔伊米尔2015年

@Taemyr可能,但是OP明确排除了这个问题
Ben Aaronson

9

给出非常普通的示例,这很难说。如果在域的情况下“重置属性”在语义上有意义,则对您的类的使用者来说更有意义的是调用

MyObject.Reset(); // Sets all necessary properties to null

MyObject = new MyClass();

我永远都不需要让您的课堂电话的消费者

MyObject.Prop1 = null;
MyObject.Prop2 = null; // and so on

如果该类表示可以重置的内容,则应通过一种Reset()方法公开该功能,而不是依靠调用构造函数或手动设置其属性。


5

正如Harrison Paine和Brandin所建议的那样,我将重用同一对象,并在Reset方法中分解属性的初始化:

public class MyClass
{
    public MyClass() { this.Reset() }

    public void Reset() {
        this.Prop1 = whatever
        this.Prop2 = you name it
        this.Prop3 = oh yeah
    }

    public object Prop1 { get; set; }

    public object Prop2 { get; set; }

    public object Prop3 { get; set; }
}

正是我想要回答的。这是两全其美的选择-并且取决于用例,唯一可行的选择(实例池)
Falco 2015年

2

如果某个类的预期使用模式是单个所有者将保留对每个实例的引用,则其他代码将不会保留该引用的副本,并且所有者多次需要多次循环是很普遍的,“填写”一个空白实例,暂时使用它,再也不需要它了(满足该条件的普通类将是StringBuilder),那么从性能的角度来看,该类包含一种将实例重置为类似方法的方法可能会很有用新条件。如果替代方案仅需要创建数百个实例,那么这种优化的价值就不大了,但是创建数百万或数十亿个对象实例的成本可能会加起来。

与此相关的是,有一些模式可以用于需要在对象中返回数据的方法:

  1. 方法创建新对象;返回参考。

  2. 方法接受对可变对象的引用,并将其填写。

  3. 方法接受引用类型的变量作为ref参数,并在适当时使用现有对象,或更改变量以标识新对象。

第一种方法通常在语义上是最简单的。第二个在调用方有点尴尬,但是如果调用方经常能够创建一个对象并使用数千次,则可能会提供更好的性能。第三种方法在语义上有点尴尬,但是如果某个方法需要返回数组中的数据并且调用者不知道所需的数组大小,则该方法可能会有所帮助。如果调用代码持有对数组的唯一引用,则使用对较大数组的引用重写该引用在语义上等同于简单地使数组变大(从语义上讲这是所需的行为)。尽管List<T>在许多情况下使用数组可能比使用手动调整大小的数组更好,但结构数组比结构列表提供更好的语义和更好的性能。


1

我认为大多数回答支持创建新对象的人都缺少一个关键场景:垃圾回收(GC)。在创建大量对象的应用程序(例如游戏或科学应用程序)中,GC可能会对性能产生重大影响。

假设我有一个表示数学表达式的表达式树,其中内部节点是函数节点(ADD,SUB,MUL,DIV),叶节点是终端节点(X,e,PI)。如果我的节点类具有Evaluate()方法,则它将可能递归地调用子代并通过Data节点收集数据。至少,所有叶节点都需要创建一个Data对象,然后可以在树上继续使用这些叶节点,直到对最终值进行评估为止。

现在让我们说我有成千上万的这些树,并且我对它们进行了循环评估。所有这些数据对象都将触发GC,并导致性能下降,这是一个很大的问题(对于我的应用程序中的某些运行,CPU利用率损失高达40%-我已经运行了探查器来获取数据)。

可能的解决方案?使用完这些数据对象后,只需对它们调用一些.Reset()即可。叶节点将不再调用“ new Data()”,而是将调用一些处理对象生命周期的Factory方法。

我知道这一点是因为我有一个解决了此问题的应用程序,并且此问题得以解决。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.