Створити новий об’єкт або скинути кожну властивість?


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 місце для ініціалізації властивостей (конструктор) і ви можете легко оновити його.

Уявіть, що ви додаєте нове властивість до класу, ви б краще оновити конструктор, ніж додати новий метод, який також повторно ініціалізує всі властивості.

Зараз є випадки, коли ви, можливо, захочете повторно використовувати об'єкт - той, де властивість дуже дорого переініціалізувати, і ви хочете зберегти її. Однак це було б більш спеціально, і у вас були б спеціальні методи для повторної ініціалізації всіх інших властивостей. Ви все ще хочете іноді створити новий об’єкт навіть для цієї ситуації.


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

Як підказують Гаррісон Пайн та Брандін, я б повторно використовував той самий об'єкт і розподілив фактори ініціалізації властивостей методом Скидання:

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; }
}

Саме те, що я хотів відповісти. Це найкраще з обох світів - І залежно від випадку використання, єдиний життєздатний вибір (Instance-Pooling)
Falco,

2

Якщо призначений шаблон використання для класу полягає в тому, що один власник буде зберігати посилання на кожен екземпляр, жоден інший код не зберігатиме копії посилань, і для власників дуже часто виникають петлі, які потрібно багато разів ", заповнити "порожній екземпляр, використовувати його тимчасово і більше ніколи не потрібен (звичайний клас, що відповідає такому критерію StringBuilder), тоді може бути корисним з точки зору продуктивності для класу, щоб включити метод для скидання екземпляра до" нова умова. Така оптимізація, швидше за все, не коштує багато, якщо альтернатива вимагатиме створення лише декількох сотень екземплярів, але вартість створення мільйонів чи мільярдів екземплярів об'єктів може додати.

У відповідній примітці є кілька моделей, які можна використовувати для методів, яким потрібно повернути дані в об'єкт:

  1. Метод створює новий об’єкт; повертає посилання.

  2. Метод приймає посилання на об'єкт, що змінюється, і заповнює його.

  3. Метод приймає змінну типу посилання як refпараметр і або використовує наявний об'єкт, якщо це підходить, або змінює змінну для ідентифікації нового об'єкта.

Перший підхід часто найпростіший семантично. Другий - трохи незручний для виклику, але може запропонувати кращу продуктивність, якщо абонент часто зможе створити один об’єкт і використовувати його тисячі разів. Третій підхід є трохи незручним семантично, але може бути корисним, якщо методу потрібно буде повернути дані в масив, і абонент не знатиме необхідний розмір масиву. Якщо викличний код містить єдине посилання на масив, перезапис цього посилання з посиланням на більший масив буде семантично еквівалентний простому збільшенню масиву (що є бажаною поведінкою семантично). Хоча використання List<T>може бути приємнішим, ніж використання масиву вручну розміром у багатьох випадках, масиви структур пропонують кращу семантику та кращу ефективність, ніж списки структур.


1

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

Скажімо, у мене є дерево виразів, яке представляє математичний вираз, де у внутрішніх вузлах знаходяться функціональні вузли (ADD, SUB, MUL, DIV), а у листкових вузлах - кінцеві вузли (X, e, PI). Якщо у мого класу вузлів є метод Evaluate (), він, ймовірно, рекурсивно викликає дітей та збиратиме дані через вузол даних. Як мінімум, всі вузли листя знадобляться для створення об’єкта даних, і тоді вони можуть повторно використовуватися на шляху до дерева, поки не буде оцінено остаточне значення.

Тепер скажімо, що у мене є тисячі цих дерев, і я оцінюю їх у циклі. Усі ці об'єкти даних запускають GC та спричиняють велику ефективність (до 40% втрати використання процесора для деяких запусків у моєму додатку - я запустив профілер для отримання даних).

Можливе рішення? Повторно використовуйте ці об'єкти даних і просто зателефонуйте до них .Reset () після їх використання. Вузоли листів більше не називатимуть "нові дані ()", вони називатимуть деякий Фабричний метод, який обробляє життєвий цикл об'єкта.

Я знаю це, тому що у мене є програма, яка потрапила в цю проблему, і це вирішило її.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.