Создать новый объект или сбросить каждое свойство?

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

        public object Prop2 { get; set; }

        public object Prop3 { get; set; }
    }

Предположим , у меня есть объект myObjectиз MyClassи мне нужно сбросить свои свойства, что лучше создать новый объект или переназначить каждое свойство? Предположим, у меня нет никакого дополнительного использования со старым экземпляром.

myObject = new MyClass();

или

myObject.Prop1 = null;
myObject.Prop2 = null;
myObject.Prop3 = null;
Колокола
источник
14
Не может быть ответа без контекста.
CodesInChaos
2
@CodesInChaos, действительно. Конкретный пример, в котором создание новых объектов может быть не предпочтительным: объединение объектов и попытка минимизировать выделения для работы с GC.
XNargaHuntress
@ XGundam05 Но даже тогда очень маловероятно, что этот метод, предложенный в ОП, является подходящим способом борьбы с ним
Бен Ааронсон,
2
Suppose I have an object myObject of MyClass and I need to reset its properties- Если требуется сброс свойств вашего объекта или если это полезно для моделирования проблемной области, почему бы не создать reset()метод для MyClass. См. Ответ HarrisonPaine ниже
Брандин

Ответы:

49

Создание нового объекта всегда лучше, тогда у вас есть 1 место для инициализации свойств (конструктор) и вы можете легко обновить его.

Представьте, что вы добавляете новое свойство в класс, вы скорее обновите конструктор, чем добавите новый метод, который также повторно инициализирует все свойства.

Теперь есть случаи, когда вы можете захотеть повторно использовать объект, когда свойство очень дорого переинициализируется, и вы захотите сохранить его. Однако это будет более специализированный метод, и у вас будут специальные методы для повторной инициализации всех других свойств. Вы все еще хотите создать новый объект иногда даже для этой ситуации.

gbjbaanb
источник
6
Возможный третий вариант: myObject = new MyClass(oldObject);. Хотя это будет означать точную копию оригинального объекта в новом экземпляре.
AmazingDreams
Я думаю, что new MyClass(oldObject)это лучшее решение для случаев, когда инициализация стоит дорого.
rickcnagy
8
Копирующие конструкторы больше в стиле C ++. .NET, похоже, предпочитает метод Clone (), который возвращает новый экземпляр, который является точной копией.
Эрик
2
Конструктор может легко ничего не делать, кроме как вызывать публичный метод Initialize (), так что у вас все еще есть та единственная точка инициализации, которая может быть вызвана в любой точке для эффективного создания «нового» нового объекта. Я использовал библиотеки, которые имеют что-то вроде интерфейса IInitialize для объектов, которые можно использовать в пулах объектов и повторно использовать для производительности.
Чед Шуггинс
16

Вы определенно должны предпочесть создание нового объекта в подавляющем большинстве случаев. Проблемы с переназначением всех свойств:

  • Требуются публичные сеттеры для всех свойств, что существенно ограничивает уровень инкапсуляции, который вы можете предоставить
  • Знание, есть ли у вас какое-либо дополнительное использование для старого экземпляра, означает, что вам нужно везде знать, что используется старый экземпляр. Поэтому, если и класс, Aи класс Bявляются переданными экземплярами класса, Cони должны знать, будут ли им когда-либо передаваться один и тот же экземпляр, и если да, то другой ли он все еще использует. Это тесно связывает классы, которые в противном случае не имеют причин быть.
  • Приводит к повторному коду - как указывал gbjbaanb, если вы добавите параметр в конструктор, везде, где вызывается конструктор, не удастся скомпилировать, нет опасности пропустить точку. Если вы просто добавите общедоступное свойство, вам нужно будет обязательно вручную находить и обновлять каждое место, где объекты «сбрасываются».
  • Увеличивает сложность. Представьте, что вы создаете и используете экземпляр класса в цикле. Если вы используете свой метод, то теперь вы должны выполнить отдельную инициализацию в первый раз в цикле или перед началом цикла. В любом случае вам нужно написать дополнительный код для поддержки этих двух способов инициализации.
  • Означает, что ваш класс не может защитить себя от недопустимого состояния. Представьте, что вы написали Fractionкласс с числителем и знаменателем и хотели обеспечить, чтобы он всегда уменьшался (т. Е. Gcd числителя и знаменателя было 1). Это невозможно сделать красиво, если вы хотите разрешить людям устанавливать числитель и знаменатель публично, поскольку они могут переходить через недопустимое состояние, чтобы перейти из одного действительного состояния в другое. Например, 1/2 (действительный) -> 2/2 (недействительный) -> 2/3 (действительный).
  • Не совсем идиоматично для языка, на котором вы работаете, увеличивая когнитивные трения для любого, кто поддерживает код.

Это все довольно серьезные проблемы. И то, что вы получаете взамен за дополнительную работу, которую вы создаете, ... ничего. Создание экземпляров объектов, как правило, невероятно дешево, поэтому выигрыш в производительности почти всегда будет незначительным.

Как уже упоминалось в другом ответе, единственное время, которое может быть связано с эффективностью, - это если ваш класс выполняет довольно дорогостоящие работы по строительству. Но даже в этом случае, чтобы эта техника работала, вам нужно будет уметь отделять дорогостоящую деталь от свойств, которые вы сбрасываете, так что вы могли бы использовать вместо этого шаблон с наименьшим весом или аналогичный.


Как примечание, некоторые из вышеперечисленных проблем могут быть несколько смягчены, если не использовать сеттеры, а вместо этого иметь public Resetметод для вашего класса, который принимает те же параметры, что и конструктор. Если по какой-то причине вы захотите пойти по этому пути сброса, это, вероятно, будет гораздо лучшим способом сделать это.

Тем не менее, дополнительная сложность и повторение, которые добавляют, наряду с пунктами выше, которые он не рассматривает, все еще являются очень убедительным аргументом против этого, особенно когда они сопоставляются с несуществующими преимуществами.

Бен Ааронсон
источник
Я думаю, что гораздо более важный вариант использования для сброса, а не для создания нового объекта, - это если вы действительно сбрасываете объект. IE. если вы хотите, чтобы другие классы, которые указывают на объект, увидели свежий объект после его сброса.
Taemyr
@Taemyr Возможно, но ОП явно исключает это в вопросе
Бен Ааронсон
9

Учитывая очень общий пример, трудно сказать. Если «сброс свойств» имеет смысл в случае домена, это будет более логично для потребителя вашего класса

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

чем

MyObject = new MyClass();

Я НИКОГДА не потребовал бы, чтобы потребитель вашего класса звонил

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

Если класс представляет что-то, что может быть сброшено, он должен предоставлять эту функциональность через Reset()метод, а не полагаться на вызов конструктора или ручную настройку его свойств.

Харрисон Пейн
источник
5

Как предполагают Харрисон Пейн и Брандин, я бы повторно использовал один и тот же объект и стал бы факторизовать инициализацию свойств в методе 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
2

Если предполагаемый шаблон использования класса состоит в том, что один владелец будет хранить ссылку на каждый экземпляр, никакой другой код не будет сохранять копии ссылок, и владельцы будут очень часто иметь циклы, которые должны много раз " заполните «пустой экземпляр», используйте его временно и никогда больше не понадобиться (общий класс соответствует такому критерию StringBuilder), тогда с точки зрения производительности может быть полезно включить в класс метод сброса экземпляра для новое состояние. Такая оптимизация вряд ли будет стоить много, если альтернатива потребует создания нескольких сотен экземпляров, но стоимость создания миллионов или миллиардов экземпляров объекта может сложиться.

Относительно примечания, есть несколько шаблонов, которые можно использовать для методов, которые должны возвращать данные в объекте:

  1. Метод создает новый объект; возвращает ссылку.

  2. Метод принимает ссылку на изменяемый объект и заполняет его.

  3. Метод принимает переменную ссылочного типа в качестве refпараметра и либо использует существующий объект, если это необходимо, либо изменяет переменную для идентификации нового объекта.

Первый подход часто является самым простым семантически. Второе немного неудобно для вызывающей стороны, но может предложить лучшую производительность, если вызывающая сторона часто сможет создать один объект и использовать его тысячи раз. Третий подход немного неудобен семантически, но может быть полезен, если метод должен будет возвращать данные в массиве, и вызывающая сторона не будет знать требуемый размер массива. Если вызывающий код содержит единственную ссылку на массив, переписывание этой ссылки со ссылкой на больший массив будет семантически эквивалентно простому увеличению массива (что является желаемым поведением семантически). Хотя использование List<T>может быть более приятным, чем использование массива с ручным изменением размера во многих случаях, массивы структур предлагают лучшую семантику и лучшую производительность, чем списки структур.

Supercat
источник
1

Я думаю, что большинство людей, отвечающих за создание нового объекта, упускают критический сценарий: сборку мусора (GC). GC может значительно снизить производительность в приложениях, создающих множество объектов (например, в играх или научных приложениях).

Допустим, у меня есть дерево выражений, которое представляет математическое выражение, где во внутренних узлах находятся функциональные узлы (ADD, SUB, MUL, DIV), а конечные узлы являются терминальными узлами (X, e, PI). Если в моем классе узлов есть метод Evaluate (), он, вероятно, будет рекурсивно вызывать дочерние элементы и собирать данные через узел данных. По крайней мере, все конечные узлы должны будут создать объект данных, а затем их можно будет повторно использовать по пути вверх по дереву, пока не будет оценено окончательное значение.

Теперь предположим, что у меня есть тысячи таких деревьев, и я оцениваю их в цикле. Все эти объекты данных будут запускать GC и вызывать значительное снижение производительности (до 40% потери загрузки ЦП для некоторых запусков в моем приложении - я запустил профилировщик для получения данных).

Возможное решение? Повторно используйте эти объекты данных и просто вызовите некоторые .Reset () для них после того, как вы закончите использовать их. Листовые узлы больше не будут вызывать 'new Data ()', они будут вызывать некоторый метод Factory, который обрабатывает жизненный цикл объекта.

Я знаю это, потому что у меня есть приложение, которое решило эту проблему, и это решило ее.

Джонатан Лаверинг
источник