sirve - ¿Por qué debería evitar el uso de propiedades en C#?
que es set en programacion (14)
En su excelente libro, CLR Via C #, Jeffrey Richter dijo que no le gustan las propiedades y recomienda no usarlas. Dio alguna razón, pero realmente no entiendo. ¿Alguien puede explicarme por qué debería o no usar propiedades? En C # 3.0, con propiedades automáticas, ¿cambia esto?
Como referencia, agregué las opiniones de Jeffrey Richter:
• Una propiedad puede ser de solo lectura o de solo escritura; el acceso de campo siempre es legible y escribible. Si define una propiedad, es mejor ofrecer métodos de acceso get y set.
• Un método de propiedad puede lanzar una excepción; el acceso al campo nunca arroja una excepción.
• Una propiedad no se puede pasar como un parámetro de salida o ref a un método; un campo puede. Por ejemplo, el siguiente código no se compilará:
using System;
public sealed class SomeType
{
private static String Name
{
get { return null; }
set {}
}
static void MethodWithOutParam(out String n) { n = null; }
public static void Main()
{
// For the line of code below, the C# compiler emits the following:
// error CS0206: A property or indexer may not
// be passed as an out or ref parameter
MethodWithOutParam(out Name);
}
}
• Un método de propiedad puede tomar mucho tiempo para ejecutarse; el acceso al campo siempre se completa de inmediato. Una razón común para usar propiedades es realizar la sincronización de subprocesos, que puede detener el subproceso para siempre y, por lo tanto, no se debe usar una propiedad si se requiere sincronización de subprocesos. En esa situación, se prefiere un método. Además, si se puede acceder a su clase de forma remota (por ejemplo, su clase se deriva de System.MashalByRefObject), llamar al método de propiedad será muy lento, y por lo tanto, se prefiere un método a una propiedad. En mi opinión, las clases derivadas de MarshalByRefObject nunca deben usar propiedades.
• Si se llama varias veces seguidas, un método de propiedad puede devolver un valor diferente cada vez; un campo devuelve el mismo valor cada vez. La clase System.DateTime tiene una propiedad Readonly Now que devuelve la fecha y la hora actuales. Cada vez que consulta esta propiedad, devolverá un valor diferente. Esto es un error, y Microsoft desea que puedan arreglar la clase haciendo de Now un método en lugar de una propiedad.
• Un método de propiedad puede causar efectos secundarios observables; el acceso al campo nunca lo hace. En otras palabras, un usuario de un tipo debería ser capaz de establecer varias propiedades definidas por un tipo en cualquier orden que elija sin notar ningún comportamiento diferente en el tipo.
• Un método de propiedad puede requerir memoria adicional o devolver una referencia a algo que no es realmente parte del estado del objeto, por lo que modificar el objeto devuelto no tiene ningún efecto sobre el objeto original; consultar un campo siempre devuelve una referencia a un objeto que se garantiza que forma parte del estado del objeto original. Trabajar con una propiedad que devuelve una copia puede ser muy confuso para los desarrolladores, y esta característica a menudo no está documentada.
Bueno, tomemos sus argumentos uno por uno:
Una propiedad puede ser de solo lectura o solo escritura; el acceso de campo siempre es legible y escribible.
Este es un triunfo para las propiedades, ya que tiene un control de acceso más detallado.
Un método de propiedad puede arrojar una excepción; el acceso al campo nunca arroja una excepción.
Si bien esto es cierto en su mayoría, puede llamar a un método en un campo de objeto no inicializado y lanzar una excepción.
• Una propiedad no se puede pasar como un parámetro de salida o ref a un método; un campo puede.
Justa.
• Un método de propiedad puede tomar mucho tiempo para ejecutarse; el acceso al campo siempre se completa de inmediato.
También puede tomar muy poco tiempo.
• Si se llama varias veces seguidas, un método de propiedad puede devolver un valor diferente cada vez; un campo devuelve el mismo valor cada vez.
No es verdad. ¿Cómo sabes que el valor del campo no ha cambiado (posiblemente por otro hilo)?
La clase System.DateTime tiene una propiedad Readonly Now que devuelve la fecha y la hora actuales. Cada vez que consulta esta propiedad, devolverá un valor diferente. Esto es un error, y Microsoft desea que puedan arreglar la clase haciendo de Now un método en lugar de una propiedad.
Si es un error, es menor.
• Un método de propiedad puede causar efectos secundarios observables; el acceso al campo nunca lo hace. En otras palabras, un usuario de un tipo debería ser capaz de establecer varias propiedades definidas por un tipo en cualquier orden que elija sin notar ningún comportamiento diferente en el tipo.
Justa.
• Un método de propiedad puede requerir memoria adicional o devolver una referencia a algo que no es realmente parte del estado del objeto, por lo que modificar el objeto devuelto no tiene ningún efecto sobre el objeto original; consultar un campo siempre devuelve una referencia a un objeto que se garantiza que forma parte del estado del objeto original. Trabajar con una propiedad que devuelve una copia puede ser muy confuso para los desarrolladores, y esta característica a menudo no está documentada.
La mayoría de las protestas podrían decirse también de los captadores y establecedores de Java, y los tuvimos durante bastante tiempo sin tales problemas en la práctica.
Creo que la mayoría de los problemas podrían resolverse mediante un mejor resaltado de la sintaxis (es decir, diferenciando las propiedades de los campos) para que el programador sepa qué esperar.
El argumento asume que las propiedades son malas porque parecen campos, pero pueden hacer acciones sorprendentes. Esta suposición es invalidada por las expectativas de los programadores de .NET:
Las propiedades no se ven como campos. Los campos se parecen a las propiedades.
• Un método de propiedad puede lanzar una excepción; el acceso al campo nunca arroja una excepción.
Entonces, un campo es como una propiedad que nunca arrojará una excepción.
• Una propiedad no se puede pasar como un parámetro de salida o ref a un método; un campo puede.
Por lo tanto, un campo es como una propiedad, pero tiene capacidades adicionales: pasar a un método de aceptación de ref
/ out
.
• Un método de propiedad puede tomar mucho tiempo para ejecutarse; el acceso al campo siempre se completa de inmediato. [...]
Entonces, un campo es como una propiedad rápida.
• Si se llama varias veces seguidas, un método de propiedad puede devolver un valor diferente cada vez; un campo devuelve el mismo valor cada vez. La clase System.DateTime tiene una propiedad Readonly Now que devuelve la fecha y la hora actuales.
Por lo tanto, un campo es como una propiedad que garantiza devolver el mismo valor a menos que el campo se haya establecido en un valor diferente.
• Un método de propiedad puede causar efectos secundarios observables; el acceso al campo nunca lo hace.
De nuevo, un campo es una propiedad que está garantizada para no hacer eso.
• Un método de propiedad puede requerir memoria adicional o devolver una referencia a algo que no es realmente parte del estado del objeto, por lo que modificar el objeto devuelto no tiene ningún efecto sobre el objeto original; consultar un campo siempre devuelve una referencia a un objeto que se garantiza que forma parte del estado del objeto original. Trabajar con una propiedad que devuelve una copia puede ser muy confuso para los desarrolladores, y esta característica a menudo no está documentada.
Este puede ser sorprendente, pero no porque sea una propiedad que hace esto. Más bien, es que casi nadie devuelve copias mutables en propiedades, por lo que el 0,1% de caso es sorprendente.
En el año 2009, este consejo me pareció simplemente un estúpido de la variedad Who Moved My Cheese . Hoy en día, es casi ridículamente obsoleto.
Un punto muy importante del que muchas respuestas parecen andar de puntillas, pero que no abordan directamente, 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 getter y setter. Esta es una ventaja sobre los campos. Un patrón común es tener un getter público y un setter interno o protegido , una técnica de herencia muy útil que no es alcanzable solo por los campos.
Lanza una excepción. Hasta la fecha, este sigue siendo uno de los métodos más eficaces de validación, especialmente cuando se trabaja con marcos de interfaz de usuario que implican conceptos vinculantes de datos. Es mucho más difícil garantizar que un objeto permanezca en un estado válido cuando se trabaja con campos.
Tómate un largo tiempo para ejecutar. La comparación válida aquí es con métodos , que toman igual tiempo - no 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 diferentes valores de su getter en ejecuciones posteriores. Esto casi parece una broma en una proximidad tan cercana al punto que exalta las virtudes de
out
parámetros deref
/out
con campos, cuyo valor de un campo después de una llamada deref
/out
es bastante diferente de su valor anterior, e impredeciblemente .Si estamos hablando del caso específico (y prácticamente académico) de acceso de un solo subproceso sin acoplamientos aferentes, se entiende bastante bien que es solo un mal diseño de propiedad tener efectos secundarios que cambian el estado visible, y tal vez mi memoria esté desvaneciéndose, pero parece que no puedo recordar ningún ejemplo de personas que usan
DateTime.Now
y esperan que el mismo valor salga cada vez. Al menos no hay casos en los que no lo hayan estropeado igual de mal con un hipotéticoDateTime.Now()
.Causa efectos secundarios observables, que es, por supuesto, precisamente la razón por la cual se inventaron las propiedades como una característica del lenguaje en primer lugar. Las propias pautas de diseño de propiedades de Microsoft indican que el orden del colocador no debería importar, ya que de lo contrario implicaría un acoplamiento temporal . Ciertamente, no puede lograr el acoplamiento temporal solo con los campos, pero eso es solo porque no puede causar ningún comportamiento significativo en absoluto con campos solo, hasta que se ejecute algún método.
Los descriptores de propiedad pueden ayudar a prevenir ciertos tipos de acoplamiento temporal al forzar el objeto a un estado válido antes de tomar cualquier acción; por ejemplo, si una clase tiene un
StartDate
y unEndDate
, establecer elEndDate
antes delStartDate
podría forzar elStartDate
nuevo también. Esto es cierto incluso en entornos multihilo o asíncronos, incluido el ejemplo obvio de una interfaz de usuario basada en eventos.
Otras cosas que pueden hacer las propiedades que campos no pueden incluir:
- Carga diferida , una de las maneras más efectivas de prevenir errores de orden de inicialización.
- Cambiar notificaciones , que son prácticamente la base completa de la arquitectura MVVM .
- Inheritance , por ejemplo, definir un
Type
oName
abstracto para que las clases derivadas puedan proporcionar metadatos interesantes pero no obstante constantes sobre ellos mismos. - Interception , gracias a lo anterior.
- Indexers , que todos los que alguna vez tuvieron que trabajar con la interoperabilidad COM y el inevitable arrojo de las llamadas del
Item(i)
lo 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 fue cuando escribió originalmente este consejo (no estoy seguro de si está en sus revisiones más recientes, sinceramente espero que no) que simplemente no quería dejar de viejos hábitos y estaba teniendo problemas para aceptar las convenciones de C # (frente a C ++, por ejemplo).
Lo que quiero decir con esto es que su argumento de "propiedades consideradas dañinas" esencialmente se reduce a una sola afirmación: las propiedades parecen campos, pero es posible que no actúen como campos. Y el problema con la declaración es que no es verdad o, en el mejor de los casos, es altamente engañosa. Las propiedades no se ven como 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 de CLR, y FXCop te gritará si no las sigues:
- 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 = 42
es un Foo.Bar = 42
propiedad o un Foo.Bar = 42
acceso de campo. Es un elemento de acceso a la propiedad y debe tratarse como cualquier otro método, puede ser lento, puede arrojar una excepción, etc. Esa es la naturaleza de la Abstraction : es enteramente de la discreción de la clase declarante cómo reaccionar. Los diseñadores de clase deberían aplicar el principio de menor sorpresa, pero las personas que llaman no deberían asumir nada sobre una propiedad, excepto que hace lo que dice en la lata. Eso es a propósito.
La alternativa a las propiedades es getter / setter methods everywhere. Ese es el enfoque de Java, y ha sido controvertido desde el principio . Está bien si ese es tu bolso, pero simplemente no es como lo hacemos en el campamento 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, verrugas adicionales para get
/ set
, o firmas de métodos adicionales, no si podemos evitarlas 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 estás leyendo miles de líneas de esto al día, hace 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", lamento decir que ha perdido completamente el punto .)
Es solo la opinión de una persona. He leído bastantes libros de c # y todavía no he visto a nadie diciendo "no use propiedades".
Personalmente, creo que las propiedades son una de las mejores cosas sobre c #. Le permiten exponer el estado a través del mecanismo que desee. Puede crear instancias perezosamente la primera vez que se utiliza algo y puede hacer validaciones al establecer un valor, etc. Al usarlas y escribirlas, solo pienso en las propiedades como setters y getters, que tienen una sintaxis mucho más agradable.
En cuanto a las advertencias con propiedades, hay un par. Uno es probablemente un mal uso de las propiedades, el otro puede ser sutil.
En primer lugar, las propiedades son tipos de métodos. Puede ser sorprendente si coloca lógica complicada en una propiedad porque la mayoría de los usuarios de una clase esperan que la propiedad sea bastante ligera.
P.ej
public class WorkerUsingMethod
{
// Explicitly obvious that calculation is being done here
public int CalculateResult()
{
return ExpensiveLongRunningCalculation();
}
}
public class WorkerUsingProperty
{
// Not at all obvious. Looks like it may just be returning a cached result.
public int Result
{
get { return ExpensiveLongRunningCalculation(); }
}
}
Encuentro que usar métodos para estos casos ayuda a hacer una distinción.
En segundo lugar, y más importante aún, las propiedades pueden tener efectos secundarios si las evalúa durante la depuración.
Digamos que tiene alguna propiedad como esta:
public int Result
{
get
{
m_numberQueries++;
return m_result;
}
}
Ahora suponga que tiene una excepción que ocurre cuando se realizan demasiadas consultas. ¿Adivina qué sucede cuando comienzas a depurar y reiniciar la propiedad en el depurador? Cosas malas. Evita hacer esto! Mirar la propiedad cambia el estado del programa.
Estas son las únicas advertencias que tengo. Creo que los beneficios de las propiedades superan con creces los problemas.
Esa razón debe haber sido dada dentro de un contexto muy específico. Por lo general, es al revés: se recomienda el uso de propiedades, ya que le dan un nivel de abstracción que le permite cambiar el comportamiento de una clase sin afectar a sus clientes ...
Hay un momento en el que considero no usar propiedades, y eso es escribir el código de .Net Compact Framework. El compilador CF JIT no realiza la misma optimización que el compilador JIT de escritorio y no optimiza los accesadores de propiedades simples, por lo que en este caso agregar una propiedad simple provoca una pequeña cantidad de exceso de código sobre el uso de un campo público. Por lo general, esto no sería un problema, pero casi siempre en el mundo de Compact Framework te enfrentas a restricciones de memoria, por lo que incluso pequeños ahorros como este cuentan.
La invocación de métodos en lugar de propiedades reduce en gran medida la legibilidad del código de invocación. En J #, por ejemplo, usar ADO.NET fue una pesadilla porque Java no admite propiedades e indexadores (que son esencialmente propiedades con argumentos). El código resultante era extremadamente feo, con llamadas a métodos paréntesis vacías por todas partes.
El soporte para propiedades e indexadores es una de las ventajas básicas de C # sobre Java.
La razón de Jeff para desagradar propiedades es porque se ven como campos, por lo que los desarrolladores que no entienden la diferencia los tratarán como si fueran campos, asumiendo que serán baratos de ejecutar, etc.
Personalmente, no estoy de acuerdo con él en este punto en particular: creo que las propiedades hacen que el código del cliente sea mucho más simple de leer que el método equivalente. Estoy de acuerdo en que los desarrolladores necesitan saber que las propiedades son básicamente métodos disfrazados, pero creo que educar a los desarrolladores sobre eso es mejor que hacer que el código sea más difícil de leer usando métodos. (En particular, habiendo visto código Java con varios getters y setters llamados en la misma declaración, sé que el código equivalente de C # sería mucho más simple de leer. La Ley de Demeter está muy bien en teoría, pero a veces foo.Name.Length
realmente es lo correcto para usar ...)
(Y no, las propiedades implementadas automáticamente no cambian nada de esto).
Esto es un poco como los argumentos en contra de usar métodos de extensión: puedo entender el razonamiento, pero el beneficio práctico (cuando se usa con moderación) supera el inconveniente en mi opinión.
No debe evitar usarlos, pero debe usarlos con calificación y cuidado, por las razones dadas por otros colaboradores.
Una vez vi una propiedad llamada algo así como Clientes que internamente abrió una llamada fuera de proceso a una base de datos y leyó la lista de clientes. El código del cliente tenía un ''para (int i a Customers.Count)'' que causaba una llamada separada a la base de datos en cada iteración y para el acceso del Cliente seleccionado. Es un ejemplo atroz que demuestra el principio de mantener la propiedad muy ligera, raramente más que un acceso interno al campo.
Un argumento PARA usar propiedades es que le permiten validar el valor que se está configurando. Otra es que el valor de la propiedad puede ser un valor derivado, no un solo campo, como TotalValue = cantidad * cantidad.
No estoy de acuerdo con Jeffrey Richter, pero puedo adivinar por qué no le gustan las propiedades (no he leído su libro).
Aunque las propiedades son solo como métodos (en la implementación), como usuario de una clase, espero que sus propiedades se comporten "más o menos" como un campo público, por ejemplo:
- no hay una operación que consuma mucho tiempo dentro de la propiedad getter / setter
- el captador de propiedades no tiene efectos secundarios (lo llama varias veces, no cambia el resultado)
Desafortunadamente, he visto propiedades que no se comportaron de esa manera. Pero el problema no son las propiedades mismas, sino las personas que las implementaron. Entonces solo requiere un poco de educación.
No he leído el libro, y no has citado la parte que no entiendes, así que tendré que adivinar.
A algunas personas no les gustan las propiedades porque pueden hacer que su código haga cosas sorprendentes.
Si Foo.Bar
, las personas que lo lean normalmente esperan que esto sea simplemente accediendo a un campo miembro de la clase Foo. Es una operación barata, casi gratuita, y es determinista. Puedo llamarlo una y otra vez, y obtener el mismo resultado cada vez.
En cambio, con propiedades, en realidad podría ser una llamada a función. Puede ser un ciclo infinito. Podría abrir una conexión de base de datos. Puede devolver diferentes valores cada vez que accedo a él.
Es un argumento similar a por qué Linus odia C ++. Su código puede sorprender al lector. Odia la sobrecarga del operador: a + b
no significa necesariamente una adición simple. Puede significar una operación muy complicada, al igual que las propiedades de C #. Puede tener efectos secundarios. Puede hacer cualquier cosa.
Honestamente, creo que este es un argumento débil. Ambos idiomas están llenos de cosas como esta. (¿Deberíamos evitar la sobrecarga del operador en C # también? Después de todo, el mismo argumento puede usarse allí)
Las propiedades permiten la abstracción. Podemos pretender que algo es un campo regular, y usarlo como si fuera uno, y no tener que preocuparnos por lo que sucede detrás de escena.
Eso generalmente se considera algo bueno, pero obviamente depende de que el programador escriba abstracciones significativas. Sus propiedades deben comportarse como campos. No deberían tener efectos secundarios, no deberían realizar operaciones costosas o inseguras. Queremos poder pensar en ellos como campos.
Sin embargo, tengo otra razón para encontrarlos menos que perfectos. No se pueden pasar por referencia a otras funciones.
Los campos se pueden pasar como ref
, permitiendo que una función llamada acceda a él directamente. Las funciones se pueden pasar como delegados, lo que permite que una función llamada acceda a ella directamente.
Propiedades ... no puede.
Eso apesta.
Pero eso no significa que las propiedades sean malas o no deberían usarse. Para muchos propósitos, son geniales.
No puedo evitar elucidar los detalles de las opiniones de Jeffrey Richter:
Una propiedad puede ser de solo lectura o solo escritura; el acceso de campo siempre es legible y escribible.
Incorrecto: los campos pueden marcarse como de solo lectura, por lo que solo el constructor del objeto puede escribir en ellos.
Un método de propiedad puede arrojar una excepción; el acceso al campo nunca arroja una excepción.
Incorrecto: la implementación de una clase puede cambiar el modificador de acceso de un campo de público a privado. Los intentos de leer campos privados en el tiempo de ejecución siempre darán como resultado una excepción.
No veo ningún motivo por el cual no deba usar Propiedades en general.
Las propiedades automáticas en C # 3+ solo simplifican la sintaxis un poco (un azúcar sintáctico la).
Personalmente, solo uso propiedades al crear métodos get / set simples. Me alejo de eso cuando vengo a estructuras de datos complicadas.