2009-03-06 20 views
7

Al configurar algunas propiedades de tipo de referencia para un proyecto en el que estoy trabajando, encontré algunas propiedades que debían inicializarse adecuadamente para ser utilizadas y nunca deberían ser nulas. He visto algunas maneras de manejar esto y realmente no puedo determinar si hay algún inconveniente importante en alguna de las principales formas en que he visto manejar esto. Me gustaría obtener la opinión de la comunidad sobre la mejor manera de manejar esto y cuáles podrían ser los posibles inconvenientes de cada método.Manejo de C# Propiedades que no deberían ser nulas

Dada una clase simple, he visto varias formas de manejar asegurándose de que una propiedad no tiene una versión nula de esta clase en una propiedad

public class MyClass 
{ 
    //Some collection of code 
} 

Opción 1 - inicializar el almacén de respaldo

public class OtherClass1 
    { 
     private MyClass _mC = new MyClass(); 
     public MyClass MC 
     { 
      get { return _mC; } 
      set { _mC = value; } 
     } 
    } 

Opción 2 - inicializar la propiedad en el constructor

public class OtherClass2 
    { 
     public MyClass MC { get; set; }  

     public OtherClass2() 
     { 
      MC = new MyClass(); 
     } 
    } 

Opti el 3 - Manejar la inicialización, según sea necesario en el Getter

public class OtherClass3 
    { 
     private MyClass _mC; 
     public MyClass MC 
     { 
      get 
      { 
       if (_mC == null) 
        _mC = new MyClass(); 
       return _mC; 
      } 
      set { _mC = value; } 
     } 
    } 

Estoy seguro de que hay otras maneras, pero estos son los que vienen a la mente y que he visto. Principalmente estoy tratando de determinar si hay una mejor práctica bien establecida sobre esto o si hay una preocupación específica con cualquiera de los anteriores.

Saludos,

Steve

+2

Aquí hay una Sintaxis "get" más compacta para la opción 3: 'get {return _mC ?? (_mC = new MyClass); } ' – Mas

Respuesta

6

mejor opción a menos que realmente puede salirse con sólo la creación de una nueva instancia a sí mismo: sólo proporcionan constructores que tienen todos los valores requeridos, y validarlos en ese punto.

+0

Jon. Gracias por el comentario. En la situación que provocó esto, existen propiedades de ObservableCollection en Silverlight ViewModel que pueden estar VACÍAS, pero no pueden ser nulas, ya que nunca destruimos la instancia inicial, solo agregamos y eliminamos contenido. PUEDO salirse con la suya. –

+0

También tengo curiosidad por su opinión sobre el uso del atributo de valor predeterminado. –

+0

El atributo de valor predeterminado solo lo usa el diseñador, hasta donde yo sé. También podría ser utilizado por serialización, etc., pero no por el código normal "solo crea una instancia". –

1

Parafraseando una pregunta que he publicado hace unos días, pero creo que puede ser útil para hacer cumplir las reglas de código y asegurarse de que una re valores nulos no se usa en el que no quiere que:

Microsoft acaba de lanzar Code Contracts, una herramienta que se integra con Visual Studio y le permite definir contratos para su código .Net y obtener el tiempo de ejecución y comprobando el tiempo de compilación.

Mire el video on Channel 9 que muestra cómo se usa.

Por ahora es un add-on, pero será parte de la Biblioteca de clases base en .Net 4.0

0

Suponiendo que no hay efectos secundarios con respecto a cuando _mc se instancia, (es decir, todo lo demás igual) , Prefiero la opción 3 ya que eso ahorra la sobrecarga de una instancia adicional de MyClass en el montón en el caso en que nunca se llama al getter de MC.

2

Por lo que sé, existe no una buena práctica establecida aquí por una razón simple: cada una de sus opciones tiene un perfil de rendimiento/memoria diferente. La primera opción es apropiada para una referencia a un objeto que usted sabe que debe ser instanciado en una clase que está seguro que será utilizado. Honestamente, sin embargo, nunca tomo este enfoque porque creo que el # 2 es simplemente más apropiado; solo una sensación de que esto es lo que un constructor es para.

La última opción es apropiada cuando usted es no seguro si se utilizará una opción. Le permite tomar el recurso solo según sea necesario.

Por cierto, esta pregunta está "al lado" de una serie de otros problemas, como el uso apropiado del patrón Singleton, el uso de clases abstractas o interfaces para su objeto diferido, etc. que podrían serle útiles explorar para obtener una mayor comprensión.

Actualización: Me parece que hay al menos un caso en el que es apropiado inicializar una instancia en la definición de la clase (su Opción n.º 1). Si la instancia será estático, entonces este es el único lugar apropiado a inicialícela:

private static readonly DateTime firstClassDate = DateTime.Parse("1/1/2009 09:00:00 AM"); 

pensé en esto al crear la línea de código anterior en algunas pruebas unitarias que estaba escribiendo hoy (el sólo lectura siendo opcional por mi punto, pero apropiado en mi caso).

0

La opción (3) tiene la ventaja de no asignar el objeto hasta que sea necesario, se puede adaptar fácilmente para ser un objeto cargado de retraso (por lo tanto, al cargar un objeto desde una base de datos, mantenga presionadas las teclas externas para cargar el objeto secundario completo cuando sea necesario)

0

opciones 1 & 2 son sintácticamente diferentes pero esencialmente equivalentes. La opción 3 es un enfoque de inicio lento que uso de manera muy consistente.
Creo que todos tienen sus usos, y depende de lo que necesite.

0

En primer lugar, ¿la propiedad nunca debe ser nula o nula en la inicialización? Sospecho que se refería a lo primero, en cuyo caso su código setter necesita evitar que se establezcan valores nulos.

El patrón básico aquí es que el estado de la clase externa no es válido si el campo de la clase interna no tiene una asignación válida. En ese caso, no solo debería el colocador defender el campo de nulo, sino que el constructor debería asegurarse de que se inicializó al valor correcto.

Su código implica que la clase externa puede instanciar la clase interna sin más entradas del código de consumo. En el mundo real, la clase externa necesita más información del exterior para recibir una instancia existente de la clase interna o suficiente información para recuperarla.

+0

Gracias por el comentario. Estás en lo correcto en tu suposición. Acabo de lanzar un código de muestra y no me molesté en hacer la comprobación del setter. En ese caso, sin embargo, eso hubiera sido importante. –

0

La opción 1 es la manera de vainilla. Ya en los viejos tiempos no teníamos propiedades auto-implementadas (con {get; set;} sintax) así que esa era la forma de especificar el comportamiento predeterminado.

Cuando se implementa automáticamente, ya que no está utilizando directamente el campo que almacena el valor predeterminado (_mC), surge una pregunta bastante buena: "¿Dónde lo inicializo?"

  • Opción 2 se llama carga ansiosos: tan pronto como se crea la clase.
  • Opción 3 Se llama carga lenta: tan pronto como lo necesite.

he visto que la forma comúnmente aceptada es la Opción 2: deseoso de carga, pero creo que esto es sólo para minimizar el código, que es completamente aceptable. El problema surge cuando comienzas a tener múltiples constructores con múltiples firmas y terminas apuntando a todos ellos a un void Initialize() método de algún tipo.

Particularmente prefiero la Opción 3, ya que es más declarativa por campo/propiedad y está optimizada para la memoria.

Eche un vistazo a EntityFramework con los diferentes sabores: Code-First o Database-first y notará cómo para las propiedades integradas utiliza cargadores ansiosos mientras que para las propiedades de navegación favorece (por defecto, puede personalizarse) lazy cargadores

Además, tenga en cuenta lo que está sucediendo en estos cargadores. En Entity Framework significa que cada vez que lo inicializa realiza un viaje a la base de datos y consulta una parte de él. Es posible que su DBA le moleste demasiado rápido por tener varias sesiones simultáneas y que desee centralizar algunas transacciones en cargas útiles individuales, por lo tanto, podría volver a utilizarse un nuevo cableado para hacer cargadores entusiastas ... aunque terminará consultando grandes cantidades de datos y ralentizar su UX.

En Código-Primera se puede ver en el siguiente ejemplo:

public class Blog 
{ 
    public int BlogId { get; set; } 
    public string Name { get; set; } 
    public string Url { get; set; } 
    public string Tags { get; set; } 

    public virtual ICollection<Post> Posts { get; set; } 
} 

En lo anterior, la colección Mensajes está declarada como virtual, ya que se puede modificar en tiempo de ejecución dependiendo de la configuración. Si lo configura para la carga ansiosa, lo hará como Opción 2, mientras que si lo establece en perezoso, que será modificada similar a Opción 3

la esperanza de que era útil

Cuestiones relacionadas