c#

c# - ¿Hay una manera más eficiente de definir propiedades públicas similares



(11)

Cuando me he enfrentado a esto en el pasado, he usado fragmentos de código personalizados en VS para crear fácilmente las propiedades. Vea el enlace HERE .

Luego, cuando tenga que agregar una nueva propiedad, simplemente invoque el fragmento de código y complete los nombres de los elementos según sea necesario.

Aunque esto no necesariamente niega la necesidad de tener muchas propiedades similares, hace que su creación sea más fácil (en la misma línea que las plantillas T4 como se mencionó anteriormente).

Tengo una clase con casi 20 propiedades públicas. Estas propiedades tienen en común que todas son cadenas y están llenas de datos de diferentes tablas de una base de datos.

Además, el conjunto es bastante normal, mientras que la obtención es especial, ya que necesito llamar a un método específico. Esto se hace para cada propiedad en este momento (ver más abajo).

Mi pregunta aquí es: ¿Existe otra forma más eficiente de hacer esto, por lo tanto, una manera en la que no tenga que definir cada propiedad pública a mano de esta manera?

class myclass { private string _Firstname; private string _Lastname; ..... public string Firstname { get { return ModifyStringMethod(this._Firstname); } set { this._Firstname = value; } } }

Como se mencionó anteriormente, cada propiedad pública se ve igual. Get recibe ModifyStringMethod con el miembro privado dado como parámetro, mientras que el conjunto solo establece el miembro privado.


Otra opción es similar a la solución de Dion V., pero utiliza la conversión implícita para hacer que la propiedad se comporte como una cadena normal desde el exterior y hace posible utilizar una propiedad automática simple. Pero esto solo funciona si ModifyStringMethod es estático y no requiere parámetros fuera de la clase.

public struct EllipsisString { private string _value; public string Value {get { return _value; }} public EllipsisString(string value) { _value = value; } // implicit conversion from string so it is possible to just assign string to the property public static implicit operator EllipsisString(string value) { return new EllipsisString(value); } public static implicit operator string(EllipsisString original) { return SpecialMethod(original.Value); } public override string ToString() { return SpecialMethod(Value); } private static string SpecialMethod(string value) { return value + "..."; } }

Y el uso es simple:

private EllipsisString FirstName { get; set; } public void Method() { FirstName = "Thomas"; Console.WriteLine(FirstName); Console.WriteLine(FirstName.Value); }


Podría definir su clase personalizada para heredar de DynamicObject

public class MyExpando : DynamicObject { Dictionary<string, object> dictionary = new Dictionary<string, object>(); //Want to create properties on initialization? Do it in the constructor public MyExpando() { dictionary.Add("PreferredName", "Darth Sidious"); dictionary.Add("GreatDialog", "Something, something, darkside!"); } public override bool TryGetMember(GetMemberBinder binder, out object result) { bool success = dictionary.TryGetValue(binder.Name, out result); if (success) result = ModifiedValue(result); return success; } public override bool TrySetMember(SetMemberBinder binder, object value) { dictionary[binder.Name] = value; return true; } private string ModifiedValue(object val) { //Modify your string here. if (val.ToString() != "Darth Sidious") return "Something something complete"; return val.ToString(); } }

¿Quieres crear una propiedad que no esté en el constructor? Entonces puedes simplemente hacer

dynamic x = new MyExpando(); x.FirstName = "Sheev"; x.LastName = "Palpatine" Console.WriteLine(x.PreferredName + " says : /"" + x.GreatDialog + "/""); Console.ReadKey();

Lo mejor de esto es que puedes implementar INotifyPropertyChanged y luego llamarlo con tu método TrySetMember , también puedes restringir el acceso a tu configurador o captador de "Propiedades" simplemente lanzando una excepción basada en el nombre de la propiedad en TryGetMember o TrySetMember y simplemente cambiar el modificador de acceso del diccionario para las clases que heredan de MyExpando para simular la herencia de propiedades.

Ejemplo de cómo restringir el acceso al establecedor de propiedades.

public override bool TrySetMember(SetMemberBinder binder, object value) { if (!dictionary.ContainsKey(binder.Name)) return false; dictionary[binder.Name] = value; return true; }


Podrías probar la generación automática de código usando la plantilla T4 . Son perfectos cuando tiene un patrón de código simple y repetitivo y no espera que algunos casos sean ligeramente diferentes de los demás.

Simplemente defina un XML con una lista de nombres de propiedades y haga que la plantilla T4 genere una clase parcial con cada propiedad.


Puede crear un fragmento de código personalizado aquí es un ejemplo de un fragmento que he creado para mí para automatizar la creación de propiedades con notificación de cambios, puede usar eso como plantilla:

<?xml version="1.0" encoding="utf-8" ?> <CodeSnippets xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet"> <CodeSnippet Format="1.0.0"> <Header> <Title>propnot</Title> <Shortcut>propnot</Shortcut> <Description>Code snippet for property and backing field with property change event</Description> <Author>Radin Gospodinov</Author> <SnippetTypes> <SnippetType>Expansion</SnippetType> </SnippetTypes> </Header> <Snippet> <Declarations> <Literal> <ID>type</ID> <ToolTip>Property type</ToolTip> <Default>int</Default> </Literal> <Literal> <ID>property</ID> <ToolTip>Property name</ToolTip> <Default>MyProperty</Default> </Literal> <Literal> <ID>field</ID> <ToolTip>The variable backing this property</ToolTip> <Default>myVar</Default> </Literal> </Declarations> <Code Language="csharp"> <![CDATA[private $type$ $field$; public $type$ $property$ { get { return this.$field$;} set { if(this.$field$ != value) { $field$ = value; this.RaisePropertyChanged(() => this.$property$); } } } $end$]]> </Code> </Snippet> </CodeSnippet> </CodeSnippets>


Si puede hacer que sus propiedades sean virtuales, puede usar un interceptor con Castle Dynamic Proxy.

Un interceptor contiene un comportamiento que puede ejecutarse cuando llama a un método determinado. En este caso, ModifyStringMethod al valor de retorno de una propiedad de cadena.

Cómo:

1) Añadir referencia al paquete nuget Castle.Core

2) Define tu interceptor

public class ModifyStringMethodInterceptor : IInterceptor { public void Intercept(IInvocation invocation) { invocation.Proceed(); if (invocation.Method.Name.StartsWith("get_") && invocation.Method.ReturnType == typeof(string)) { invocation.ReturnValue = ModifyStringMethod((string)invocation.ReturnValue); } } private static string ModifyStringMethod(string input) { return (input ?? "") + "MODIFIED"; } }

El ejemplo anterior tiene un método de intercepción que se llamará cuando se llame a su propiedad. Puede ver en el ejemplo invocation.Proceed () esto continúa la llamada a la propiedad.

Luego verifica si es una propiedad get_ y devuelve una cadena

if (invocation.Method.Name.StartsWith("get_") && invocation.Method.ReturnType == typeof(string))

Luego modifica el valor de retorno del método.

invocation.ReturnValue = ModifyStringMethod((string)invocation.ReturnValue);

3) Defina los objetos a los que desea agregar este comportamiento con un método virtual (note que también puedo usar propiedades implementadas automáticamente aquí) BONIFICACIÓN

public class Intercepted { public virtual string A { get; set; } }

4) Luego cree instancias de objetos usando la clase ProxyGenerator en DynamicProxy,

p.ej

public class Program { static void Main(string[] args) { var pg = new ProxyGenerator(); // Intercepted will be an instance of Intercepted class with the // ModifyStringMethodInterceptor applied to it var intercepted = pg.CreateClassProxy<Intercepted>(new ModifyStringMethodInterceptor()); intercepted.A = "Set ... "; Console.WriteLine(intercepted.A); Console.ReadLine(); } }

La salida es

Set ... MODIFIED

El beneficio aquí es que sus objetos están "limpios", por ejemplo, no necesitan conocer el ModifyStringMethodInterceptor y pueden contener propiedades implementadas automáticamente, que si tiene muchos de estos objetos reducirá la cantidad de código en gran cantidad.

Avanzando un paso más si necesitara un mayor control, podría aplicar este comportamiento agregando un atributo a la clase, por ejemplo

[AttributeUsage(AttributeTargets.Method)] public class ModifyStringMethodAttribute : Attribute { }

Entonces los objetos se definen como:

public class Intercepted { public virtual string A { [ModifyStringMethod] get; set; } }

Y un cambio al interceptor:

if (invocation.Method.ReturnType == typeof(string) && invocation.Method.GetCustomAttributes(true) .OfType<ModifyStringMethodAttribute>().Any()) { invocation.ReturnValue = ModifyStringMethod((string)invocation.ReturnValue); }

Para comprobar el atributo, y luego aplicar el método de llamada.


Si realmente desea seguir este enfoque, entonces la generación de código con plantillas como T4 o CodeSmith es probablemente el camino a seguir, sin embargo, estoy de acuerdo con @DanBryant en que construir sus propiedades de esta manera puede llevar a una clase contra intuitiva. Espero que un código como este funcione:

X.FirstName = "Some random long name"; Assert.AreEqual("Some random long name", X.FirstName);

Por su comentario, con el diseño de su clase esto podría no funcionar (dependiendo de la longitud del truncamiento en su ModifyStringMethod , en realidad puede obtener X.FireName == "Some rand..." . Esto parece incorrecto.

Un mejor enfoque puede ser implementar la modificación fuera del comportamiento de la propiedad, posiblemente en un método de extensión. Así que algo como esto:

public static class FormatStringExtensions { public static string ModifyStringForOutput(this string me) { if (me.Length > 10) { return me.Substring(0, 10) + "..."; } return me; } };

Le permitiría definir sus clases de datos usando propiedades automáticas:

public class myclass { public string FirstName { get; set; } public string LastName {get; set; } };

Y luego modifique el valor de la cadena de las propiedades cuando sea apropiado usando el método de extensión:

var instance = new myclass(); instance.FirstName = "01234567890123"; Console.WriteLine("Original Name {0}/nModified Name {1}/n", instance.FirstName, instance.FirstName.ModifyStringForOutput());

Esto permite que sus propiedades sigan funcionando como se espera de las propiedades normales, al tiempo que le brinda una manera fácil de acceder a las cadenas con formato si es necesario.


Sin embargo, personalmente no soy un fan de esta solución, podrías hacer algo como esto:

class MyClass { private IDictionary<string, string> propertyValueByName = new Dictionary<string, string>(); public string this[string propertyName] { get { return propertyValueByName[propertyName]; } set { propertyValueByName[propertyName] = ModifyStringMethod(value); } } public string FirstName { get { return this["FirstName"]; } set { this["FirstName"] = value; } } public string LastName { get { return this["LastName"]; } set { this["LastName"] = value; } } }


Un ejemplo utilizando la reflexión.

class MyClass { public string FirstName { private get; set; } public string LastName { private get; set; } public string GetModifiedValue(string propertyName) { var prop = this.GetType().GetProperty(propertyName); return ModifyStringMethod((string)prop.GetValue(this, null)); } }

Entonces, para obtener cada valor modificado en lugar de usar MyClass.FirstName , MyClass.FirstName MyClass.GetModifiedValue("FirstName")


Una alternativa es crear un fragmento de código simple utilizando el nombre de tipo y propiedad como variable. Es mucho más rápido generar la clase y mantienes el control total sobre tu código.


PostSharp es otra alternativa.

Solo aplica un atributo sobre la clase y escribe sus propiedades con "get; set;" sintaxis.

PostSharp es una herramienta .NET que permite a los desarrolladores aplicar aspectos del código que se ejecutarán a sus conjuntos, espacios de nombres, clases o métodos.

Específicamente, PostSharp permite a los desarrolladores escribir menos código aplicando atributos a bloques de código que luego tendrán el código que refleja ese aspecto y se ejecutan con el bloque de código seleccionado. Este enfoque reduce significativamente la "plomería" que es redundante en una base de código.

Los casos de uso comunes incluyen lo siguiente:

  • Explotación florestal

  • Seguridad

  • Deshacer rehacer

  • INotifyPropertyChanged

  • Manejo de excepciones