2009-05-27 18 views
8

Estoy trabajando en C# y estoy empezando a jugar con propiedades. Una cosa que no sé es cuál es la mejor manera/dónde colocar la lógica para los descriptores de acceso de las propiedades de clase y cómo manejar los errores.C# Propiedades - Establecer pregunta

Por ejemplo, decir que tengo esta clase (básico):

class Person 
{ 
    private int _Age = 18; 

    public Person() 
    { 

    } 

    public int Age 
    { 
     get 
     { 
      return _Age; 
     } 
     set 
     { 
      _Age = value; 
     } 
    } 
} 

Ahora dicen que tengo un requisito en la propiedad Edad, 0 < Edad < 100. ¿Dónde pongo la lógica para esto?

¿Debo ponerlo en la propiedad?

public int Age 
    { 
     get 
     { 
      return _Age; 
     } 
     set 
     { 
      if (value < 0 || value > 99) 
       // handle error 
      else 
       _Age = Convert.ToInt32(value); 
     } 
    } 

oa través de la clase que está creando un objeto Person?

static void Main(string[] args) 
{ 
    Person him = new Person(); 
    int NewAge = -10; 

    if (NewAge < 0 || NewAge > 100) 
     // handle error 
    else 
     him.Age = NewAge; 
} 

¿Qué sucede si hay un problema con NewAge (no cumple con mi restricción)? ¿Debo crear una excepción personalizada y lanzar eso? ¿Debo simplemente imprimir un mensaje que diga una edad válida?

He hecho algunas búsquedas en Google y no encuentro nada que responda completamente a mis preguntas. Necesito un libro: -/

+3

Tenga en cuenta también que el colocador no necesita 'Convert.ToInt32 (valor)' ... porque la propiedad es un 'int', el' valor' también será un 'int'. – jerryjvl

+0

¿No se considera esto una programación defensiva? ¿O lo llevo lejos? –

+0

Lo estás llevando demasiado lejos. No hay posibilidad de que lo que obtienes no sea un int. –

Respuesta

23

Utilice el ajustador de propiedades, está ahí por esa razón (agregando funcionalidad a un campo).

Si se transfiere el valor fuera de rango, puede lanzar ArgumentOutOfRangeException o simplemente establecerlo en el valor mínimo (o máximo), pero esto depende de los requisitos del proceso.

+3

+1 por sugerir tirar ArgumentOutOfRangeException y no solo Exception –

+0

Agregaría un pequeño matiz a esto al decir que el setter de la propiedad es generalmente el lugar apropiado, pero SOLO para cosas que está absolutamente seguro debe ser cierto en todo momento . Los controles que pueden tener excepciones (perdón por el juego de palabras) probablemente deberían entrar en un método de validación, de modo que el usuario de la clase pueda determinar si el error es apropiado. La razón principal para decir esto es que no es irracional en el caso general permitir edades mayores a 99 o a 100 (que la pregunta original no permite por alguna razón) – jerryjvl

+0

Yo: 1) documentaré claramente el comportamiento y el requisito de esta propiedad (para que el usuario sepa cómo usarla) y 2) ponga un mensaje claro sobre la excepción lanzada 3) esto creará un código limpio (no ensuciado con métodos de validación que confundirán a futuros desarrolladores) –

3

Colóquelo en la propiedad. ¡Ese es uno de los propósitos principales de las propiedades!

Considere que pondrá la responsabilidad en el nivel más bajo posible. El ejemplo que proporcionó no requiere nada más que el parámetro value para tomar una decisión. Ni siquiera depende de otros miembros de la misma clase. No hay ninguna razón para que el resto de la clase sepa cómo funciona la validez de la propiedad Age, y ciertamente no hay razón para que otra parte del código lo sepa.

6

Querrá poner la lógica en el setter y lanzar una excepción si no cumple con los requisitos. Sin embargo, también querrás crear un método estático IsValidAge o algo así para que las clases que crean una Persona puedan verificar el valor de edad en lugar de solo ver si arroja una excepción. Alternativamente, podría tener propiedades de MinAge y MaxAge para que el código de llamada pueda verificar si la edad que están a punto de establecer está entre ellos.

No cree un tipo de excepción personalizado, use ArgumentOutOfRangeException o algo así.

3

Es posible que desee examinar la interfaz IDataErrorInfo.

Al implementar esta interfaz en su clase, abrirá la clase a otros mecanismos que pueden beneficiarse de la información de error adicional.

0

Utilice el ajustador y use ArgumentOutOfRangeException, como ya se ha dicho.

A veces llamará a otro método, normalmente algo como OnAgeChanged (int), donde podría realizar la validación (que puede ser una llamada a otro método para que pueda usarlo en cualquier parte) e invocar un controlador de eventos, que si está conectado, puede tener otra lógica para aplicar dependiendo de cómo está usando su objeto. Eso puede no ser necesario en su escenario, pero es bastante común hacer eso para las propiedades. Especialmente si va a actualizar un formulario, el formulario se conectará al evento AgeChanged para actualizarse.

1

Voy a jugar al abogado del diablo y le doy una respuesta por la cual NO QUISIERA ponerlo en el colocador. Hay muchos casos en los que desea poder establecer el valor de su propiedad Age y, posteriormente, pregunte si ese objeto es válido en sentido holístico.

Por ejemplo, mantener su propiedad sencilla:

public int Age { get; set; } 

Luego, cuando un valor no válido se pasa en que puede tener alguna función IsValid que indica si el objeto en cuestión está bien. Esto puede ser extremadamente útil porque puedes hacer una validación más complicada además de una simple restricción de edad.

bool IsValid() 
{ 
    if (Age < 0 || Age > 99) 
    return false; 
} 

Para algo tan simple como este no hay una gran cantidad de beneficios, pero vea también que se puede utilizar esto en su capa de persistencia para que pueda asegurarse de que cualquier objeto que no es válida nunca será persistió. En esos casos, no necesariamente desea lanzar una excepción.

Considera también esto:

DateTime StartDate { get; set; } 
DateTime EndDate { get; set;} 

bool IsValid() 
{ 
    return StartDate > EndDate  
} 

Esto sólo es pseudocódigo, pero usted consigue mi punto. Esto es algo que no se puede hacer dentro de un setter, o al menos, no de una manera que se pueda mantener.

+0

Este es un punto interesante de vista. Aunque estoy de acuerdo en que tener una función independiente de IsValid podría ser útil, no entiendo por qué puedo crear una condición en la que el modelo en sí no sea válido. ¿Recuperar un error tan pronto como sea posible reduce los dolores de cabeza en el futuro? ¿Qué sucede cuando el código de persistencia se olvidó de llamar a IsValid? Esa es una gran cosa para recordar. –

11

me gustaría ponerlo en práctica como esto:

public int Age 
{ 
    get 
    { 
     return _Age; 
    } 
    set 
    { 
     if (IsValidAge(value)) 
      _Age = value; 
     else 
      throw new ArgumentOutOfRangeException("value", string.Format("value should be between {0} and {1} inclusive.", MinAge, MaxAge)); 
    } 
} 

private bool IsValidAge(int age) 
{ 
    return (age >= MinAge && age <= MaxAge); 
} 

Un par de cosas a tener en cuenta:

  • no cambian su valor en lugar de lanzar una excepción, esto es un comportamiento inesperado.
  • El .NET framework arroja excepciones de Argument * en setters, así que diría que es una buena idea seguir esta práctica. En este caso, ArgumentOutOfRangeException es perfecto, IMO.
  • Cuando se hace referencia al argumento en mensajes de excepción y documentos xml, el estándar es llamar al argumento "valor", no el nombre de su propiedad.
  • Recomendaría MinAge y MaxAge como ventajas privadas en su clase, no caiga en la trampa de los mensajes de error de codificación con límites de rango en ellos, no hay nada peor que decir "5 en inválido, ingrese un número entre 1 y 10 "cuando alguien cambia la especificación más tarde, pero se olvida de actualizar una cadena.
+0

Una serie de buenos puntos en esta respuesta. –

+0

Estoy de acuerdo con una implementación como esta. Definitivamente debe hacer la validación en el conjunto y debe ser una llamada externa. También recomendaría moverlo a un lugar más accesible si es algo que quizás quieras usar en otro lugar. – JonBWalsh

0

Si está haciendo un diseño factorial (es decir, es importante que el estado del objeto sea válido en todos los puntos durante la vida del objeto), puede establecer y verificar esto en el constructor. En el diseño de un componente, debe verificar esto en el método que usa la propiedad, p.

class Person 
{ 
    public int calculateTimeToExpiration() 
    { 
    if (Age < 0 || Age > 100) 
    //throw 
    } 
} 

static void main es el código del cliente y no es un buen lugar para la lógica de negocio.

0

Gracias por las respuestas! En caso de que tengas curiosidad aquí, es lo que cambio mi pequeño ejemplo (aunque ahora es más detallado) (basado en los comentarios).

class Person 
{ 
    private string _FirstName = "Joe"; 
    private string _LastName = "Smith"; 
    private int _Age = 18; 

    private const int MinAge = 1; 
    private const int MaxAge = 99; 

    public Person() 
    { 

    } 

    public Person(string FirstName, string LastName, int Age) 
    { 
     this.FirstName = FirstName; 
     this.LastName = LastName; 
     this.Age = Age; 
    } 

    public string FirstName 
    { 
     get 
     { 
      return _FirstName; 
     } 
     set 
     { 
      _FirstName = value; 
     } 
    } 

    public string LastName 
    { 
     get 
     { 
      return _LastName; 
     } 
     set 
     { 
      _LastName = value; 
     } 
    } 

    public int Age 
    { 
     get 
     { 
      return _Age; 
     } 
     set 
     { 
      if (IsValidAge(value)) 
       throw new ArgumentOutOfRangeException("value","Please enter a positive age less than 100."); 
      else 
       _Age = value; 
     } 
    } 

    private bool IsValidAge(int age) 
    { 
     return (age < MinAge || age > MaxAge); 
    } 

    public override string ToString() 
    { 
     if (Age == 1) 
      return "My name is " + FirstName + " " + LastName + " and I am " + Age + " year old."; 
     else 
      return "My name is " + FirstName + " " + LastName + " and I am " + Age + " years old."; 
    } 
} 

static void Main(string[] args) 
    { 
     Person him, her; 

     try 
     { 
      him = new Person("Joe Bob", "McGruff", 1); 
      Console.WriteLine(him); 
     } 
     catch (ArgumentOutOfRangeException range) 
     { 
      Console.WriteLine(range.Message); 
     } 

     try 
     { 
      her = new Person(); 
      her.Age = -5; 
      Console.WriteLine(her); 
     } 
     catch (ArgumentOutOfRangeException range) 
     { 
      Console.Write(range.Message); 
     } 

     Console.ReadKey(); 
    } 
+0

Cambiaría la lógica en su método IsValidAge. Ya estás llamando al método "IsValid", estás respondiendo verdadero para inválido. Además, no es necesario convertir.ToInt en su setter. El valor será automáticamente un int. Recuerde, las propiedades son solo azúcar sintáctico para los métodos. Debajo del capó se ha sintonizado en un vacío privado set_Age (int x) {..} donde x -> value. Por lo tanto, NO HAY MANERA para que el valor NO sea un int. – BFree

0

Su respuesta actualizada es buena pero se debe limpiar una pequeña pieza en su colocador/validación

public int Age 
    { 
     get 
     { 
      return _Age; 
     } 
     set 
     { 
      if (!IsValidAge(value)) 
       throw new ArgumentOutOfRangeException("Age","Please enter a positive age less than 100."); 

      _Age = value; 
     } 
    } 

    private bool IsValidAge(int age) 
    { 
     return (age > MinAge && age < MaxAge); 
    }