c# - tipos - que es tipado dinamico en python
¿Hay un patrón de C#para los miembros de clase fuertemente tipados con métodos externos de configuración/obtención? (6)
.Net 4.5 o más nuevo
Si usa .Net 4.5 o más reciente, puede usar el CallerMemberNameAttribute para poder llamarlo así:
public string CustomerNumber {
get { return Get(); }
}
Para hacer que esto funcione, modifique el método Get agregando el atributo al parámetro:
private string Get([CallerMemberName] string key)
{
...
}
Aviso de rendimiento: el compilador insertará una cadena como parámetro en el sitio de la llamada, por lo que esto es rápido.
.Net 4.0 o anterior
Si usa .Net 4.0 o una versión anterior, aún puede usar nombres de propiedades fuertemente tipados en lugar de escribir cadenas manualmente, pero necesita implementar un método como este para extraer el nombre de la propiedad de una Expression , y luego puede llamarlo usando una expresión :
public string CustomerNumber {
get { return Get(() => this.CustomerNumber ); }
}
Los setters se pueden implementar de la misma manera.
Aviso de rendimiento: la cadena se extrae en tiempo de ejecución, por lo que es más lento que usar CallerMemberNameAttribute.
Tengo la siguiente estructura y me gustaría una solución con los dos beneficios de las siguientes dos clases. La primera clase está utilizando cadenas y miembros fuertemente tipados:
public class UserSessionData
{
private string Get(string key)
{
throw new NotImplementedException("TODO: Get from external source");
}
private void Set(string key, string value)
{
throw new NotImplementedException("TODO: Set in external source");
}
public string CustomerNumber {
get { return Get("CustomerNumber"); }
set { Set("CustomerNumber", value); }
}
public string FirstName {
get { return Get("FirstName"); }
set { Set("FirstName", value); }
}
public string LastName {
get { return Get("LastName"); }
set { Set("LastName", value); }
}
// ... a couple of hundreds of these
}
Puedo imaginar que una alternativa sea un método Get
and Set
con un parámetro enum
. Aquí está la segunda clase:
public class UserSessionData
{
public enum What {
CustomerNumber, FirstName, LastName, // ...
}
public string Get (What what) { return MyExternalSource(what); }
public string Set (What what, string value) { return MyExternalSource(what); }
}
Pero el lado del consumidor de la clase # 2 no es bonito:
UserSessionData.Get(UserSessionData.What.CustomerNumber)
Compárelo con la primera clase: UserSessionData.CustomerNumber
¿Existe una forma fuertemente tipada de llamar a los métodos Get y Set en mi ejemplo de primera clase? Dicho de otra manera: ¿Cómo obtengo los beneficios de ambas clases, es decir, la capacidad de mantenimiento de los miembros con una tipificación fuerte y una sintaxis atractiva?
¿Qué tal una enumeración con algunos métodos de extensión, como este:
// usage:
[TestMethod]
public void example()
{
UserSessionData.CustomerNumber.Set("cust num");
Assert.AreEqual("cust num", UserSessionData.CustomerNumber.Get());
}
// implementation:
public enum UserSessionData
{
CustomerNumber,
FirstName,
}
public static class UserSessionDataHelper
{
private static Dictionary<string, string> values = new Dictionary<string, string>();
private static string GetName(this UserSessionData field)
{
return Enum.GetName(typeof(UserSessionData), field);
}
public static string Get(this UserSessionData field)
{
return values[field.GetName()];
}
public static void Set(this UserSessionData field, string value)
{
values[field.GetName()] = value;
}
}
Este patrón permite claves de recursos débilmente acoplados. Un mapa de hash que exceptúa cualquier tipo de clave, con el propósito de extensibilidad. A menudo se ve esto con los mensajes del sistema en contenedores IoC.
En mi experiencia, este patrón otorga gran flexibilidad con el costo de la documentación requerida. Considere resolver el problema con la convención en lugar de la implementación. Intento apegarme a los valores enumerados donde sea posible.
- Son primitivos inmutables con nombres fuertes.
- Se pueden agrupar en su tipo de padre.
- Se pueden iterar de manera bastante simple si es necesario.
- Responden a la refactorización.
No sé por qué todavía no se ha propuesto una alma tan simple:
public class UserSessionData
{
private string Get(What what)
{
throw new NotImplementedException("TODO: Get from external source");
}
private void Set(What what, string value)
{
throw new NotImplementedException("TODO: Set in external source");
}
public string CustomerNumber {
get { return Get(What.CustomerNumber); }
set { Set(What.CustomerNumber, value); }
}
// ...
}
public enum What
{
CustomerNumber, FirstName, LastName, // ...
}
Y el uso que tanto le gustó: userSessionData.CustomerNumber
Si su "fuente externa" prefiere cadenas, puede convertir What
enum en privado y convertir enum a cadena.
Puedes usar una plantilla T4 para generar la clase. En la plantilla T4 solo nombras todas las propiedades, también puedes obtenerlas a través de la reflexión desde una enumeración, pero usar una matriz de cadenas es más simple.
Agregar nuevo elemento -> Plantilla de texto
<#@ template debug="false" hostspecific="false" language="C#" #>
<#@ output extension=".cs" #>
<# var properties = new string [] {
"CustomerNumber", // This is where you define the properties
"FirstName",
"LastName"
}; #>
class UserSessionData {
<#
foreach (string propertyName in properties)
{ #>
public string <#= propertyName #>{
get { return Get("<#= propertyName #>"); }
set { Set("<#= propertyName #>", value); }
}
<# } #>
}
Más info @ Generación de código en tiempo de diseño usando plantillas de texto T4
Si está utilizando .NET 4.0 o posterior, puede usar DynamicObject y anular sus métodos TryGetMember y TrySetMember para hacer esto completamente dinámico. Esto no será fuertemente tipado sin embargo.