Utworzyć nowy obiekt lub zresetować każdą właściwość?

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

        public object Prop2 { get; set; }

        public object Prop3 { get; set; }
    }

Załóżmy, że mam obiektu myObjectz MyClassi muszę zresetować swoje właściwości, lepiej jest utworzyć nowy obiekt lub przypisać każdą właściwość? Załóżmy, że nie mam żadnego dodatkowego zastosowania ze starą instancją.

myObject = new MyClass();

lub

myObject.Prop1 = null;
myObject.Prop2 = null;
myObject.Prop3 = null;
Dzwony
źródło
14
Naprawdę nie można odpowiedzieć bez kontekstu.
CodesInChaos
2
@CodesInChaos, rzeczywiście. Konkretny przykład, w którym tworzenie nowych obiektów może nie być preferowane: Pula obiektów i próba zminimalizowania przydziałów do obsługi GC.
XNargaHuntress
@ XGundam05 Ale nawet wtedy bardzo mało prawdopodobne jest, aby ta technika sugerowana w OP była właściwym sposobem radzenia sobie z nią
Ben Aaronson
2
Suppose I have an object myObject of MyClass and I need to reset its properties- Jeśli konieczne jest zresetowanie właściwości obiektu lub przydatne jest modelowanie domeny problemowej, dlaczego nie stworzyć reset()metody dla MyClass. Zobacz odpowiedź HarrisonPaine poniżej
Brandin

Odpowiedzi:

49

Tworzenie nowego obiektu jest zawsze lepsze, wtedy masz 1 miejsce do zainicjowania właściwości (konstruktora) i możesz go łatwo zaktualizować.

Wyobraź sobie, że dodajesz nową właściwość do klasy, wolisz zaktualizować konstruktor niż dodać nową metodę, która również ponownie inicjuje wszystkie właściwości.

Teraz są przypadki, w których możesz chcieć ponownie użyć obiektu, taki, w którym właściwość jest bardzo kosztowna w celu ponownej inicjalizacji i chcesz ją zachować. Byłby to jednak bardziej specjalistyczny specjalista i dysponowałbyś specjalnymi metodami ponownego inicjowania wszystkich innych właściwości. Nadal będziesz chciał stworzyć nowy obiekt czasami nawet w tej sytuacji.

gbjbaanb
źródło
6
Możliwe trzecia opcja: myObject = new MyClass(oldObject);. Chociaż oznaczałoby to dokładną kopię oryginalnego obiektu w nowej instancji.
AmazingDreams,
Myślę, że new MyClass(oldObject)to najlepsze rozwiązanie w przypadkach, gdy inicjalizacja jest droga.
rickcnagy
8
Konstruktory kopiowania są bardziej w stylu C ++. .NET wydaje się faworyzować metodę Clone (), która zwraca nową instancję, która jest dokładną kopią.
Eric
2
Konstruktor może z łatwością zrobić tylko wywołać publiczną metodę Initialize (), więc nadal masz ten jeden punkt inicjalizacji, który można wywołać w dowolnym punkcie w celu skutecznego utworzenia „nowego” świeżego obiektu. Użyłem bibliotek, które mają coś w rodzaju interfejsu IInitialize dla obiektów, które mogą być używane w pulach obiektów i ponownie wykorzystywane do wydajności.
Chad Schouggins,
16

Zdecydowanie wolisz stworzyć nowy obiekt w zdecydowanej większości przypadków. Problemy z ponownym przypisaniem wszystkich właściwości:

  • Wymaga publicznych ustawień dla wszystkich właściwości, co drastycznie ogranicza poziom enkapsulacji, jaki możesz zapewnić
  • Wiedząc, czy masz jakieś dodatkowe zastosowanie dla starej instancji, musisz wiedzieć, że używana jest stara instancja. Jeśli więc klasa Ai klasa Bsą zaliczonymi instancjami klasy C, muszą wiedzieć, czy kiedykolwiek przejdą tę samą instancję, a jeśli tak, to czy druga nadal z niej korzysta. To ściśle łączy klasy, które w innym przypadku nie miałyby powodu.
  • Prowadzi do powtarzającego się kodu - jak wskazał gbjbaanb, jeśli dodasz parametr do konstruktora, wszędzie, co wywoła konstruktora, kompilacja się nie powiedzie, nie ma niebezpieczeństwa pominięcia miejsca. Jeśli po prostu dodasz własność publiczną, musisz koniecznie ręcznie znaleźć i zaktualizować każde miejsce, w którym obiekty są „resetowane”.
  • Zwiększa złożoność. Wyobraź sobie, że tworzysz i używasz instancji klasy w pętli. Jeśli używasz tej metody, musisz teraz wykonać osobną inicjalizację za pierwszym razem przez pętlę lub przed jej uruchomieniem. Tak czy inaczej jest dodatkowy kod, który musisz napisać, aby obsługiwać te dwa sposoby inicjowania.
  • Oznacza, że ​​twoja klasa nie może uchronić się przed stanem nieprawidłowym. Wyobraź sobie, że napisałeś Fractionklasę z licznikiem i mianownikiem i chciałeś wymusić, aby zawsze była zmniejszona (tj. Gcd licznika i mianownika wynosi 1). Nie da się tego zrobić dobrze, jeśli chcesz, aby ludzie mogli publicznie ustawić licznik i mianownik, ponieważ mogą przechodzić przez stan nieprawidłowy, aby przejść z jednego prawidłowego stanu do drugiego. Np. 1/2 (ważny) -> 2/2 (nieprawidłowy) -> 2/3 (ważny).
  • W ogóle nie jest idiomatyczny dla języka, w którym pracujesz, zwiększając tarcie poznawcze dla każdego, kto utrzymuje kod.

To wszystkie dość znaczące problemy. A w zamian za dodatkową pracę, którą tworzysz, jest ... nic. Tworzenie instancji obiektów jest na ogół niezwykle tanie, więc korzyści związane z wydajnością prawie zawsze będą znikome.

Jak wspomniano w innej odpowiedzi, jedynym problemem, który może być istotnym problemem, jest to, czy twoja klasa wykona bardzo drogie prace przy budowie. Ale nawet w takim przypadku, aby ta technika zadziałała, będziesz musiał być w stanie oddzielić kosztowną część od właściwości, które resetujesz, dzięki czemu możesz zamiast tego użyć wzoru flyweight lub podobnego.


Na marginesie, niektóre z powyższych problemów można nieco złagodzić, nie używając setterów i zamiast tego mając public Resetw swojej klasie metodę, która przyjmuje takie same parametry jak konstruktor. Jeśli z jakiegoś powodu chciałbyś pójść tą drogą resetowania, prawdopodobnie byłby to o wiele lepszy sposób.

Jednak dodatkowa złożoność i powtarzalność, które dodają, wraz z powyższymi punktami, których nie dotyczy, są nadal bardzo przekonującym argumentem przeciwko temu, szczególnie w porównaniu z nieistniejącymi korzyściami.

Ben Aaronson
źródło
Myślę, że znacznie ważniejszym przypadkiem użycia do resetowania niż tworzenia nowego obiektu jest faktyczne resetowanie obiektu. TO ZNACZY. jeśli chcesz, aby inne klasy wskazujące obiekt widziały świeży obiekt po jego zresetowaniu.
Taemyr
@Taemyr Być może, ale OP wyraźnie wyklucza to w pytaniu
Ben Aaronson
9

Biorąc pod uwagę bardzo ogólny przykład, trudno powiedzieć. Jeśli „resetowanie właściwości” ma sens semantyczny w przypadku domeny, bardziej sensowne będzie zadzwonienie do konsumenta z twojej klasy

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

Niż

MyObject = new MyClass();

NIGDY nie wymagałbym nawiązania połączenia z konsumentem z twojej klasy

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

Jeśli klasa reprezentuje coś, co można zresetować, powinna ujawnić tę funkcjonalność za pomocą Reset()metody, zamiast polegać na wywołaniu konstruktora lub ręcznym ustawieniu jego właściwości.

Harrison Paine
źródło
5

Jak sugerują Harrison Paine i Brandin, ponownie użyłbym tego samego obiektu i rozłożyłbym parametry inicjalizacji właściwości w metodzie 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; }
}
Antoine Trouve
źródło
Dokładnie to, co chciałem odpowiedzieć. Jest to najlepszy z obu światów - i w zależności od przypadku użycia, jedyny możliwy wybór (łączenie instancji)
Falco
2

Jeżeli zamierzone wzór Wykorzystanie dla klasy jest to, że pojedynczy właściciel będzie zachować odniesienie do każdej instancji, żaden inny kod będzie przechowywać kopie referencji, i to będzie bardzo często właściciele posiadają pętle, które muszą, wiele razy " wypełnij „pustą instancję, użyj jej tymczasowo i nigdy więcej jej nie potrzebujesz (powszechna klasa spełniająca takie kryterium StringBuilder), wówczas z punktu widzenia wydajności przydatne może być włączenie metody resetowania instancji w celu polubienia- nowy warunek. Taka optymalizacja prawdopodobnie nie będzie warta wiele, jeśli alternatywa wymagałaby jedynie utworzenia kilkuset instancji, ale koszt utworzenia milionów lub miliardów instancji obiektów może się sumować.

W pokrewnej uwadze istnieje kilka wzorców, których można użyć dla metod, które muszą zwrócić dane w obiekcie:

  1. Metoda tworzy nowy obiekt; zwraca referencję.

  2. Metoda przyjmuje odwołanie do obiektu zmiennego i wypełnia go.

  3. Metoda przyjmuje zmienną typu referencyjnego jako refparametr i albo wykorzystuje istniejący obiekt, jeśli jest to odpowiednie, albo zmienia zmienną, aby zidentyfikować nowy obiekt.

Pierwsze podejście jest często najłatwiejsze semantycznie. Drugi jest nieco niewygodny po stronie wywołującej, ale może zaoferować lepszą wydajność, jeśli dzwoniący często będzie mógł utworzyć jeden obiekt i użyć go tysiące razy. Trzecie podejście jest trochę niewygodne semantycznie, ale może być pomocne, jeśli metoda będzie musiała zwrócić dane w tablicy, a osoba dzwoniąca nie będzie znać wymaganego rozmiaru tablicy. Jeśli kod wywołujący zawiera jedyne odniesienie do tablicy, przepisanie tego odwołania z odwołaniem do większej tablicy będzie semantycznie równoważne po prostu powiększeniu tablicy (co jest pożądanym zachowaniem semantycznym). Chociaż List<T>w wielu przypadkach użycie może być lepsze niż użycie tablicy o ręcznie zmienionym rozmiarze, tablice struktur oferują lepszą semantykę i lepszą wydajność niż listy struktur.

supercat
źródło
1

Myślę, że większości osób, które opowiadają się za tworzeniem nowego obiektu, brakuje krytycznego scenariusza: odśmiecanie (GC). GC może mieć prawdziwy wpływ na wydajność w aplikacjach, które tworzą wiele obiektów (np. Gry lub aplikacje naukowe).

Załóżmy, że mam drzewo wyrażeń reprezentujące wyrażenie matematyczne, w którym w węzłach wewnętrznych znajdują się węzły funkcyjne (ADD, SUB, MUL, DIV), a węzły liści są węzłami końcowymi (X, e, PI). Jeśli moja klasa węzłów ma metodę Evaluate (), prawdopodobnie będzie rekurencyjnie wywoływała dzieci i zbierała dane za pośrednictwem węzła Data. Przynajmniej wszystkie węzły liścia będą musiały utworzyć obiekt Data, a następnie będą mogły zostać ponownie wykorzystane podczas przechodzenia w górę drzewa, aż do oceny końcowej wartości.

Powiedzmy teraz, że mam tysiące tych drzew i jestem oceniany w pętli. Wszystkie te obiekty danych wywołają GC i spowodują duży spadek wydajności (do 40% utraty wykorzystania procesora dla niektórych uruchomień w mojej aplikacji - uruchomiłem profiler, aby uzyskać dane).

Możliwe rozwiązanie? Ponownie użyj tych obiektów danych i po prostu wywołaj na nich część .Reset () po ich użyciu. Węzły liścia nie będą już wywoływać „nowych danych ()”, będą wywoływać metodę Factory, która obsługuje cykl życia obiektu.

Wiem to, ponieważ mam aplikację, która dotknęła tego problemu i to go rozwiązało.

Jonathan Lavering
źródło