¿Crear un nuevo objeto o restablecer cada propiedad?

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

        public object Prop2 { get; set; }

        public object Prop3 { get; set; }
    }

Supongamos que tengo un objeto myObjectde MyClassy necesito para restablecer sus propiedades, ¿es mejor crear un nuevo objeto o reasignar cada propiedad? Suponga que no tengo ningún uso adicional con la instancia anterior.

myObject = new MyClass();

o

myObject.Prop1 = null;
myObject.Prop2 = null;
myObject.Prop3 = null;
Campanas
fuente
14
Realmente no se puede responder sin contexto.
CodesInChaos
2
@CodesInChaos, de hecho. Ejemplo específico en el que crear nuevos objetos puede no ser preferible: Agrupación de objetos e intentar minimizar las asignaciones para tratar con el GC.
XNargaHuntress
@ XGundam05 Pero incluso entonces, es muy poco probable que esta técnica sugerida en el OP sea la forma adecuada de abordarla
Ben Aaronson,
2
Suppose I have an object myObject of MyClass and I need to reset its properties- Si es necesario restablecer las propiedades de su objeto o si es útil modelar el dominio del problema, ¿por qué no crear un reset()método para MyClass? Vea la respuesta de HarrisonPaine a continuación
Brandin

Respuestas:

49

Instanciar un nuevo objeto siempre es mejor, entonces tiene 1 lugar para inicializar las propiedades (el constructor) y puede actualizarlo fácilmente.

Imagine que agrega una nueva propiedad a la clase, preferiría actualizar el constructor que agregar un nuevo método que también reinicialice todas las propiedades.

Ahora, hay casos en los que es posible que desee reutilizar un objeto, uno en el que una propiedad es muy costosa para reinicializar y desea conservarla. Sin embargo, esto sería más especializado, y tendría métodos especiales para reinicializar todas las demás propiedades. Todavía querrás crear un nuevo objeto a veces incluso para esta situación.

gbjbaanb
fuente
66
Posible tercera opción: myObject = new MyClass(oldObject);. Aunque esto implicaría una copia exacta del objeto original en una nueva instancia.
AmazingDreams
Creo que new MyClass(oldObject)es la mejor solución para los casos en que la inicialización es costosa.
rickcnagy
8
Los constructores de copia son más estilo C ++. .NET parece favorecer un método Clone () que devuelve una nueva instancia que es una copia exacta.
Eric
2
El constructor no podría hacer nada más que llamar a un método público Initialize () para que todavía tenga ese único punto de inicialización que podría ser invocado en cualquier punto para crear efectivamente un "nuevo" objeto nuevo. He usado bibliotecas que tienen algo así como una interfaz IInitialize para objetos que se pueden usar en grupos de objetos y reutilizar para el rendimiento.
Chad Schouggins
16

Definitivamente debería preferir crear un nuevo objeto en la gran mayoría de los casos. Problemas al reasignar todas las propiedades:

  • Requiere setters públicos en todas las propiedades, lo que limita drásticamente el nivel de encapsulación que puede proporcionar
  • Saber si tiene algún uso adicional para la instancia anterior significa que necesita saber en todas partes que se está utilizando la instancia anterior. Entonces, si clase Ay clase Bson instancias pasadas de clase, Centonces deben saber si alguna vez pasarán la misma instancia, y si es así, si la otra todavía la está usando. Esto une estrechamente las clases que de otra manera no tendrían razón de ser.
  • Conduce al código repetido, como lo indicó gbjbaanb, si agrega un parámetro al constructor, en todos los lugares que llaman al constructor no se compilará, no hay peligro de perder un lugar. Si solo agrega una propiedad pública, tendrá que asegurarse de buscar y actualizar manualmente cada lugar donde los objetos se "restablecen".
  • Aumenta la complejidad. Imagina que estás creando y usando una instancia de la clase en un bucle. Si usa su método, ahora tiene que hacer una inicialización separada la primera vez a través del ciclo, o antes de que comience el ciclo. De cualquier manera es un código adicional que debe escribir para admitir estas dos formas de inicialización.
  • Significa que su clase no puede protegerse de estar en un estado no válido. Imagina que escribiste una Fractionclase con un numerador y un denominador, y quisiste exigir que siempre se redujera (es decir, el mcd del numerador y el denominador era 1). Esto es imposible de hacer bien si desea permitir que las personas establezcan públicamente el numerador y el denominador, ya que pueden pasar de un estado no válido a otro para pasar de un estado válido a otro. Por ejemplo, 1/2 (válido) -> 2/2 (inválido) -> 2/3 (válido).
  • No es del todo idiomático para el lenguaje en el que está trabajando, lo que aumenta la fricción cognitiva para cualquiera que mantenga el código.

Todos estos son problemas bastante significativos. Y lo que obtienes a cambio del trabajo extra que creas es ... nada. La creación de instancias de objetos es, en general, increíblemente barata, por lo que el beneficio de rendimiento casi siempre será totalmente insignificante.

Como se mencionó en la otra respuesta, el único momento en que el rendimiento podría ser una preocupación relevante es si su clase realiza un trabajo significativamente costoso en la construcción. Pero incluso en ese caso, para que esta técnica funcione, deberá poder separar la parte costosa de las propiedades que está restableciendo, por lo que podrá usar el patrón de peso mosca o similar en su lugar.


Como nota al margen, algunos de los problemas anteriores podrían mitigarse un poco al no usar setters y en su lugar tener un public Resetmétodo en su clase que tome los mismos parámetros que el constructor. Si por alguna razón quisieras seguir esta ruta de reinicio, probablemente sería una forma mucho mejor de hacerlo.

Aún así, la complejidad y la repetición adicionales que se suman, junto con los puntos anteriores que no aborda, siguen siendo un argumento muy persuasivo en contra de hacerlo, especialmente cuando se comparan con los beneficios inexistentes.

Ben Aaronson
fuente
Creo que un caso de uso mucho más importante para restablecer en lugar de crear un nuevo objeto es si realmente está restableciendo el objeto. ES DECIR. si desea que otras clases que apuntan al objeto vean un objeto nuevo después de restablecerlo.
Taemyr
@Taemyr Posiblemente, pero el OP lo descarta explícitamente en la pregunta
Ben Aaronson
9

Dado el ejemplo muy genérico, es difícil saberlo. Si "restablecer las propiedades" tiene sentido semántico en el caso del dominio, tendrá más sentido para el consumidor de su clase llamar

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

Que

MyObject = new MyClass();

NUNCA requeriría que el consumidor de su clase llame

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

Si la clase representa algo que se puede restablecer, debería exponer esa funcionalidad a través de un Reset()método, en lugar de depender de llamar al constructor o establecer manualmente sus propiedades.

Harrison Paine
fuente
5

Como sugieren Harrison Paine y Brandin, reutilizaría el mismo objeto y factorizaría la inicialización de las propiedades en un 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
fuente
Exactamente lo que quería responder. Este es el mejor de ambos mundos, y dependiendo del caso de uso, la única opción viable (Instance-Pooling)
Falco
2

Si el patrón de uso previsto para una clase es que un solo propietario mantendrá una referencia a cada instancia, ningún otro código conservará copias de las referencias, y será muy común que los propietarios tengan bucles que necesitan, muchas veces , " complete "una instancia en blanco, úsela temporalmente y nunca la vuelva a necesitar (una clase común que cumpla ese criterio sería StringBuilder), entonces puede ser útil desde el punto de vista del rendimiento para que la clase incluya un método para restablecer una instancia a like- Nueva condición. Es probable que tal optimización no valga mucho si la alternativa solo requiere la creación de unos cientos de instancias, pero el costo de crear millones o miles de millones de instancias de objetos puede sumar.

En una nota relacionada, hay algunos patrones que se pueden usar para los métodos que necesitan devolver datos en un objeto:

  1. El método crea un nuevo objeto; devuelve referencia.

  2. El método acepta una referencia a un objeto mutable y lo completa.

  3. El método acepta una variable de tipo de referencia como refparámetro y utiliza el objeto existente si es adecuado o cambia la variable para identificar un nuevo objeto.

El primer enfoque es a menudo el más fácil semánticamente. El segundo es un poco incómodo en el lado de la llamada, pero puede ofrecer un mejor rendimiento si la persona que llama con frecuencia podrá crear un objeto y usarlo miles de veces. El tercer enfoque es un poco semántico incómodo, pero puede ser útil si un método necesita devolver datos en una matriz y la persona que llama no sabrá el tamaño de matriz requerido. Si el código de llamada contiene la única referencia a la matriz, reescribir esa referencia con una referencia a una matriz más grande será semánticamente equivalente a simplemente agrandar la matriz (que es el comportamiento semántico deseado). Si bien el uso List<T>puede ser mejor que el uso de una matriz redimensionada manualmente en muchos casos, las matrices de estructuras ofrecen una semántica mejor y un mejor rendimiento que las listas de estructuras.

Super gato
fuente
1

Creo que a la mayoría de las personas que responden a favor de crear un nuevo objeto les falta un escenario crítico: recolección de basura (GC). GC puede tener un verdadero rendimiento en aplicaciones que crean muchos objetos (juegos de pensamiento o aplicaciones científicas).

Digamos que tengo un árbol de expresión que representa una expresión matemática, donde en los nodos internos hay nodos de función (ADD, SUB, MUL, DIV), y los nodos hoja son nodos terminales (X, e, PI). Si mi clase de nodo tiene un método Evaluate (), es probable que recurra recursivamente a los hijos y recopile datos a través de un nodo de datos. Por lo menos, todos los nodos hoja necesitarán crear un objeto de Datos y luego esos pueden reutilizarse en el camino hacia el árbol hasta que se evalúe el valor final.

Ahora digamos que tengo miles de estos árboles y los evalúo en un bucle. Todos esos objetos de datos activarán un GC y causarán un impacto en el rendimiento, uno grande (hasta un 40% de pérdida de utilización de la CPU para algunas ejecuciones en mi aplicación; ejecuté un generador de perfiles para obtener los datos).

¿Una posible solución? Reutilice esos objetos de datos y solo llame a algunos .Reset () sobre ellos después de que haya terminado de usarlos. Los nodos hoja ya no llamarán 'nuevos datos ()', llamarán a algún método Factory que maneje el ciclo de vida del objeto.

Lo sé porque tengo una aplicación que ha afectado este problema y esto lo resolvió.

Jonathan Lavering
fuente