En 2009, este consejo simplemente parecía una queja de la variedad Who Moved My Cheese . Hoy en día, es casi ridículamente obsoleto.
Un punto muy importante que muchas respuestas parecen andar de puntillas, pero que no abordan de frente es que estos supuestos "peligros" de las propiedades son una parte intencional del diseño del marco.
Sí, las propiedades pueden:
Especifique diferentes modificadores de acceso para el captador y el definidor. Esta es una ventaja sobre los campos. Un patrón común es tener un captador público y un definidor interno o protegido , una técnica de herencia muy útil que no se puede lograr solo con campos.
Lanza una excepción. Hasta la fecha, este sigue siendo uno de los métodos de validación más efectivos, especialmente cuando se trabaja con marcos de interfaz de usuario que involucran conceptos de enlace de datos. Es mucho más difícil asegurarse de que un objeto permanezca en un estado válido cuando se trabaja con campos.
Tarda mucho en ejecutarse. La comparación válida aquí es con métodos , que toman el mismo tiempo, no con campos . No se da ninguna base para la afirmación "se prefiere un método" que no sea la preferencia personal de un autor.
Devuelve valores diferentes de su getter en ejecuciones posteriores. Esto casi parece una broma en una proximidad tan cercana al punto de ensalzar las virtudes de los parámetros ref/ outcon campos, cuyo valor de un campo después de una llamada ref/ outestá prácticamente garantizado que será diferente de su valor anterior, y de manera impredecible.
Si estamos hablando del caso específico (y prácticamente académico) del acceso de un solo subproceso sin acoplamientos aferentes, se comprende bastante bien que es solo un mal diseño de propiedad el tener efectos secundarios de cambio de estado visible, y tal vez mi memoria es se desvanece, pero parece que no puedo recordar ningún ejemplo de personas que usan DateTime.Nowy esperan que salga el mismo valor cada vez. Al menos no en ningún caso en el que no lo hubieran estropeado tanto con una hipótesis DateTime.Now().
Causar efectos secundarios observables, que es, por supuesto, precisamente la razón por la que las propiedades se inventaron en primer lugar como una característica del lenguaje. Las propias pautas de diseño de propiedades de Microsoft indican que el orden de los establecedores no debería importar, ya que hacerlo de otra manera implicaría un acoplamiento temporal . Ciertamente, no puede lograr el acoplamiento temporal con campos solo, pero eso es solo porque no puede hacer que ocurra ningún comportamiento significativo con campos solo, hasta que se ejecute algún método.
Los descriptores de acceso a la propiedad pueden ayudar a prevenir ciertos tipos de acoplamiento temporal al forzar el objeto a un estado válido antes de que se lleve a cabo cualquier acción; por ejemplo, si una clase tiene un StartDatey un EndDate, entonces establecer el EndDateantes del StartDatepodría forzar el StartDateretroceso también. Esto es cierto incluso en entornos asíncronos o de subprocesos múltiples, incluido el ejemplo obvio de una interfaz de usuario basada en eventos.
Otras cosas que pueden hacer las propiedades y que los campos no pueden incluir:
- Carga diferida , una de las formas más efectivas de prevenir errores de orden de inicialización.
- Notificaciones de cambios , que son prácticamente la base completa de la arquitectura MVVM .
- La herencia , por ejemplo, la definición de clases abstractas
Typeo Namederivadas puede proporcionar metadatos interesantes pero constantes sobre sí mismos.
- Intercepción , gracias a lo anterior.
- Indexadores , que todos los que alguna vez han tenido que trabajar con interoperabilidad COM y la inevitable oleada de
Item(i)llamadas reconocerán como algo maravilloso.
- Trabaje con PropertyDescriptor, que es esencial para crear diseñadores y para marcos XAML en general.
Richter es claramente un autor prolífico y sabe mucho sobre CLR y C #, pero tengo que decir que parece que cuando escribió originalmente este consejo (no estoy seguro de si está en sus revisiones más recientes, espero sinceramente que no) que simplemente no quería dejar atrás los viejos hábitos y estaba teniendo problemas para aceptar las convenciones de C # (vs. C ++, por ejemplo).
Lo que quiero decir con esto es que su argumento de "propiedades consideradas dañinas" esencialmente se reduce a una sola declaración: las propiedades parecen campos, pero pueden no actuar como campos. Y el problema con la afirmación es que no es verdad o, en el mejor de los casos, es muy engañoso. Las propiedades no parecen campos; al menos, se supone que no deben verse como campos.
Hay dos convenciones de codificación muy fuertes en C # con convenciones similares compartidas por otros lenguajes CLR, y FXCop le gritará si no las sigue:
- Los campos siempre deben ser privados, nunca públicos.
- Los campos deben declararse en camelCase. Las propiedades son PascalCase.
Por lo tanto, no hay ambigüedad sobre si Foo.Bar = 42es un descriptor de acceso de propiedad o un descriptor de acceso de campo. Es un descriptor de acceso de propiedad y debe tratarse como cualquier otro método: puede ser lento, puede generar una excepción, etc. Esa es la naturaleza de la abstracción : depende totalmente de la discreción de la clase declarante cómo reaccionar. Los diseñadores de clases deben aplicar el principio de la mínima sorpresa, pero las personas que llaman no deben asumir nada sobre una propiedad, excepto que hace lo que dice en la lata. Eso es a propósito.
La alternativa a las propiedades son los métodos getter / setter en todas partes. Ese es el enfoque de Java y ha sido controvertido desde el principio . Está bien si ese es tu bolso, pero no es así como nos movemos en el campo de .NET. Intentamos, al menos dentro de los límites de un sistema de tipo estático, evitar lo que Fowler llama ruido sintáctico . No queremos paréntesis adicionales, adicionales get/ setverrugas, o firmas de métodos adicionales - No si podemos evitarlos sin pérdida de claridad.
Di lo que quieras, pero foo.Bar.Baz = quux.Answers[42]siempre será mucho más fácil de leer que foo.getBar().setBaz(quux.getAnswers().getItem(42)). Y cuando lee miles de líneas de esto al día, marca la diferencia.
(Y si su respuesta natural al párrafo anterior es decir, "seguro que es difícil de leer, pero sería más fácil si lo dividiera en varias líneas", entonces lamento decir que no entendió por completo .)