Criar novo objeto ou redefinir todas as propriedades?

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

        public object Prop2 { get; set; }

        public object Prop3 { get; set; }
    }

Suponha que eu tenho um objeto myObjectde MyClasse eu preciso redefinir suas propriedades, é melhor criar um novo objeto ou transferir cada propriedade? Suponha que eu não tenho nenhum uso adicional com a instância antiga.

myObject = new MyClass();

ou

myObject.Prop1 = null;
myObject.Prop2 = null;
myObject.Prop3 = null;
Bells
fonte
14
Realmente não pode ser respondido sem contexto.
CodesInChaos
2
@CodesInChaos, de fato. Exemplo específico em que a criação de novos objetos pode não ser preferível: Pool de Objetos e tentativa de minimizar alocações para lidar com o GC.
XNargaHuntress
@ XGundam05 Mas, mesmo assim, é muito pouco provável que esta técnica sugerida no OP é a forma adequada de lidar com ele
Ben Aaronson
2
Suppose I have an object myObject of MyClass and I need to reset its properties- Se for necessário redefinir as propriedades do seu objeto ou se for útil modelar o domínio do problema, por que não criar um reset()método para o MyClass. Veja a resposta de HarrisonPaine abaixo
Brandin

Respostas:

49

Instanciar um novo objeto é sempre melhor, então você tem 1 lugar para inicializar as propriedades (o construtor) e pode atualizá-lo facilmente.

Imagine que você adiciona uma nova propriedade à classe, prefere atualizar o construtor do que adicionar um novo método que também reinicializa todas as propriedades.

Agora, há casos em que você pode querer reutilizar um objeto, um caso em que uma propriedade é muito cara para reinicializar e você deseja mantê-la. Isso seria mais especializado, no entanto, e você teria métodos especiais para reinicializar todas as outras propriedades. Você ainda deseja criar um novo objeto às vezes até para esta situação.

gbjbaanb
fonte
6
Possível terceira opção: myObject = new MyClass(oldObject);. Embora isso implique uma cópia exata do objeto original em uma nova instância.
AmazingDreams
Eu acho que new MyClass(oldObject)é a melhor solução para os casos em que a inicialização é cara.
rickcnagy
8
Os construtores de cópia são mais estilo C ++. O .NET parece preferir um método Clone () que retorna uma nova instância que é uma cópia exata.
Eric
2
O construtor não poderia fazer nada além de chamar um método público Initialize () para que você ainda tenha aquele único ponto de inicialização que poderia ser chamado de qualquer ponto para criar efetivamente um "novo" objeto novo. Eu usei bibliotecas que têm algo como uma interface IInitialize para objetos que podem ser usados ​​em pools de objetos e reutilizados para desempenho.
Chad Schouggins
16

Você definitivamente deve preferir criar um novo objeto no grande maioria dos casos. Problemas com a reatribuição de todas as propriedades:

  • Requer setters públicos em todas as propriedades, o que limita drasticamente o nível de encapsulamento que você pode fornecer
  • Saber se você tem algum uso adicional para a instância antiga significa que você precisa saber em todos os lugares que a instância antiga está sendo usada. Então, se classe Ae classeB são instâncias passadas da classe C, elas precisam saber se alguma vez serão passadas à mesma instância e, se sim, se a outra ainda a está usando. Isso une fortemente classes que, de outra forma, não têm razão de existir.
  • Leva a códigos repetidos - como gbjbaanb indicou, se você adicionar um parâmetro ao construtor, em qualquer lugar que chamar o construtor não será compilado, não haverá perigo de perder um local. Se você adicionar uma propriedade pública, precisará encontrar e atualizar manualmente todos os locais onde os objetos são "redefinidos".
  • Aumenta a complexidade. Imagine que você está criando e usando uma instância da classe em um loop. Se você usar seu método, agora precisará fazer a inicialização separada na primeira vez no loop ou antes de iniciar o loop. De qualquer maneira, é necessário escrever um código adicional para dar suporte a essas duas maneiras de inicializar.
  • Significa que sua classe não pode se proteger de estar em um estado inválido. Imagine que você escreveu umFraction classe com um numerador e denominador e queria reforçar que ela sempre fosse reduzida (ou seja, o MDC do numerador e denominador era 1). Isso é impossível de fazer bem, se você deseja permitir que as pessoas definam publicamente o numerador e o denominador, pois elas podem passar por um estado inválido para passar de um estado válido para outro. Por exemplo, 1/2 (válido) -> 2/2 (inválido) -> 2/3 (válido).
  • Não é idiomático para o idioma em que você está trabalhando, aumentando o atrito cognitivo para quem mantém o código.

Todos esses são problemas bastante significativos. E o que você recebe em troca do trabalho extra que você cria é ... nada. A criação de instâncias de objetos é, em geral, incrivelmente barata, portanto o benefício de desempenho quase sempre será totalmente desprezível.

Como a outra resposta mencionada, o único momento em que o desempenho pode ser uma preocupação relevante é se a sua turma fizer algum trabalho significativamente caro na construção. Mas, mesmo nesse caso, para que essa técnica funcione, você precisará separar a parte cara das propriedades que está redefinindo, para poder usar o padrão flyweight ou similar.


Como uma observação lateral, alguns dos problemas acima podem ser mitigados de certa forma por não usar setters e, em vezpublic Reset método em sua classe que os mesmos parâmetros do construtor. Se, por algum motivo, você quiser seguir essa rota de redefinição, provavelmente seria uma maneira muito melhor de fazê-lo.

Ainda assim, a complexidade e repetição adicionais que adicionam, juntamente com os pontos acima que ela não aborda, ainda são um argumento muito convincente contra fazê-lo, especialmente quando pesadas em relação aos benefícios inexistentes.

Ben Aaronson
fonte
Eu acho que um caso de uso muito mais importante para redefinir, em vez de criar um novo objeto, é se você realmente estiver redefinindo o objeto. IE. se você quiser que outras classes que apontam para o objeto vejam um novo objeto após a redefinição.
Taemyr
@Taemyr Possivelmente, mas o OP regras explicitamente que na pergunta
Ben Aaronson
9

Dado o exemplo muito genérico, é difícil dizer. Se "redefinir as propriedades" fizer sentido semântico no caso do domínio, fará mais sentido para o consumidor da sua classe chamar

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

Do que

MyObject = new MyClass();

NUNCA exigiria que o consumidor ligasse para sua classe

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

Se a classe representa algo que pode ser redefinido, ela deve expor essa funcionalidade por meio de um Reset()método, em vez de depender da chamada do construtor ou da configuração manual de suas propriedades.

Harrison Paine
fonte
5

Como sugerem Harrison Paine e Brandin, eu reutilizaria o mesmo objeto e fatoraria a inicialização das propriedades em um método 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
fonte
Exatamente o que eu queria responder. Este é o melhor dos dois mundos - E, dependendo do caso de uso, a única opção viável (Instance-Pooling)
Falco
2

Se o padrão de uso pretendido para uma classe é que um único proprietário manterá uma referência para cada instância, nenhum outro código manterá cópias das referências e será muito comum que os proprietários tenham loops que precisam, muitas vezes , " preencha "uma instância em branco, use-a temporariamente e nunca mais precise dela (uma classe comum que atenda a esse critério seriaStringBuilder ), pode ser útil do ponto de vista de desempenho para a classe incluir um método para redefinir uma instância para Nova Condição. É provável que essa otimização não valha muito se a alternativa exigir apenas a criação de algumas centenas de instâncias, mas o custo de criação de milhões ou bilhões de instâncias de objetos pode aumentar.

Em uma nota relacionada, existem alguns padrões que podem ser usados ​​para métodos que precisam retornar dados em um objeto:

  1. O método cria um novo objeto; retorna referência.

  2. O método aceita uma referência a um objeto mutável e o preenche.

  3. O método aceita uma variável do tipo de referência como refparâmetro e usa o objeto existente, se adequado, ou altera a variável para identificar um novo objeto.

A primeira abordagem é geralmente a mais fácil semanticamente. O segundo é um pouco estranho no lado da chamada, mas pode oferecer melhor desempenho se um chamador frequentemente conseguir criar um objeto e usá-lo milhares de vezes. A terceira abordagem é um pouco estranha semântica, mas pode ser útil se um método precisar retornar dados em uma matriz e o chamador não souber o tamanho da matriz necessária. Se o código de chamada contiver a única referência à matriz, a reescrita dessa referência com uma referência a uma matriz maior será semanticamente equivalente a simplesmente aumentar a matriz (que é o comportamento desejado semanticamente). Embora o uso List<T>possa ser mais agradável do que o uso de uma matriz redimensionada manualmente em muitos casos, as matrizes de estruturas oferecem melhor semântica e melhor desempenho do que as listas de estruturas.

supercat
fonte
1

Acho que a maioria das pessoas que responde a favor da criação de um novo objeto está perdendo um cenário crítico: coleta de lixo (GC). O GC pode ter um impacto real no desempenho em aplicativos que criam muitos objetos (jogos de reflexão ou aplicativos científicos).

Digamos que eu tenho uma árvore de expressão que representa uma expressão matemática, onde nos nós internos existem nós de função (ADD, SUB, MUL, DIV) e os nós folha são nós terminais (X, e, PI). Se minha classe de nó tiver um método Evaluate (), provavelmente chamará recursivamente os filhos e coletará dados por meio de um nó Data. No mínimo, todos os nós das folhas precisarão criar um objeto Data e, em seguida, eles poderão ser reutilizados no caminho até a árvore até que o valor final seja avaliado.

Agora, digamos que tenho milhares dessas árvores e as avalio em um loop. Todos esses objetos de dados acionam um GC e causam um grande impacto no desempenho (perda de até 40% na utilização da CPU para algumas execuções no meu aplicativo - executei um criador de perfil para obter os dados).

Uma solução possível? Reutilize esses objetos de dados e chame alguns .Reset () depois que você terminar de usá-los. Os nós folha não chamarão mais 'new Data ()', eles chamarão algum método Factory que manipula o ciclo de vida do objeto.

Eu sei disso porque tenho um aplicativo que atingiu esse problema e resolveu o problema.

Jonathan Lavering
fonte