with parameter multiple method has generic create attribute c# generics reflection

parameter - generic restrictions c#



Método de Parse genérico sin boxeo (7)

Creo que estás sobreestimado el impacto del boxeo / unboxing. El método de análisis tendrá una sobrecarga mucho mayor (análisis sintáctico de cadenas), empequeñeciendo la sobrecarga del boxeo. Además, todas las declaraciones if tendrán un mayor impacto. La reflexión tiene el mayor impacto de todos.

No me gustaría ver este tipo de código en producción, ya que hay una forma más limpia de hacerlo. El principal problema que tengo es la gran cantidad de instrucciones if que necesitarás para cubrir todos los casos y el hecho de que alguien podría pasarle cualquier tipo antiguo.

Lo que haría sería escribir una función de análisis para cada tipo que quiero analizar (es decir, ParseInt ()). Está más claro y está bien definido lo que la función intentará hacer. También con métodos estáticos cortos, es más probable que el compilador los alinee, guardando una llamada de función.

Creo que esta es una mala aplicación de genéricos, ¿alguna razón particular para hacerlo de esta manera?

Intento escribir un método Parse genérico que convierta y devuelva un valor fuertemente tipado de NamedValueCollection. Intenté dos métodos, pero ambos métodos están pasando por el boxeo y el desempaquetado para obtener el valor. ¿Alguien sabe una manera de evitar el boxeo? Si viera esto en producción, ¿no le gustaría, qué tan malo es para el rendimiento?

Usuage:

var id = Request.QueryString.Parse<int>("id");

Intento # 1:

public static T Parse<T>(this NameValueCollection col, string key) { string value = col[key]; if (string.IsNullOrEmpty(value)) return default(T); if (typeof(T) == typeof(int)) { //return int.Parse(value); // cannot convert int to T //return (T)int.Parse(value); // cannot convert int to T return (T)(object)int.Parse(value); // works but boxes } if (typeof(T) == typeof(long)) { return (T)(object)long.Parse(value); // works but boxes } ... return default(T); }

Intento # 2 (usando la reflexión):

public static T Parse<T>(this NameValueCollection col, string key) { string value = col[key]; if (string.IsNullOrEmpty(value)) return default(T); try { var parseMethod = typeof(T).GetMethod("Parse", new Type[] { typeof(string) }); if (parseMethod == null) return default(T); // still boxing because invoke returns an object var parsedVal = parseMethod.Invoke(null, new object[] { value }); return (T)parsedVal; } // No Proper Parse Method found catch(AmbiguousMatchException) { } return default(T); }


public static T Parse<T>(this NameValueCollection col, string key) { return (T)Convert.ChangeType(col[key], typeof(T)); }

No estoy del todo seguro de las casillas ChangeType o no (supongo que leer los documentos me dirá, pero estoy presionado por el tiempo en este momento), pero al menos se deshace de toda esa comprobación de tipos. Sin embargo, la sobrecarga del boxeo no es muy alta, así que no me preocuparía demasiado por eso. Si le preocupa la coherencia del tipo de tiempo de ejecución, escribiría la función como:

public static T Parse<T>(this NameValueCollection col, string key) { T value; try { value = (T)Convert.ChangeType(col[key], typeof(T)); } catch { value = default(T); } return value; }

De esta forma, la función no funcionará si el valor no puede convertirse por la razón que sea. Eso significa, por supuesto, que tendrá que verificar el valor devuelto (que tendría que hacer de todos modos, ya que el usuario puede editar la cadena de consulta).


int value = int.Parse(Request.QueryString["RecordID"]);


Para una mejor legibilidad, puede usar un diccionario genérico con una función anónima de la siguiente manera:

var parserFuncs = new Dictionary<Type, Func<string, object>>() { { typeof(int), p => (int) int.Parse(p) }, { typeof(bool), p => (bool) bool.Parse(p) }, { typeof(long), p => (long) long.Parse(p) }, { typeof(short), p => (short) short.Parse(p) }, { typeof(DateTime), p => (DateTime) DateTime.Parse(p) } /* ...same for all the other primitive types */ }; return (T) parserFuncs[typeof(T)](value);


Aquí hay una sugerencia para la implementación, siguiendo la lógica de Robert Wagner, pero usando un enfoque genérico para reducir la duplicación:

public static int ParseInt32(this NameValueCollection col, string key) { return Parse(col, key, int.Parse); } public static double ParseDouble(this NameValueCollection col, string key) { return Parse(col, key, double.Parse); } private static T Parse<T>(NameValueCollection col, string key, Func<string, T> parse) { string value = col[key]; if (string.IsNullOrEmpty(value)) return default(T); return parse(value); }

A decir verdad, devolver cero por una cadena vacía o nula me asusta; esto podría causar problemas si algunos valores son legítimamente cero. En cambio, quisiera que los métodos devuelvan nullables ( int? double? Etc.), que es un enfoque un poco más compacto que el patrón de parámetros TryParse utilizado para los métodos TryParse framework. Podrías hacer esto:

public static int? ParseInt32(this NameValueCollection col, string key) { return Parse(col, key, int.Parse); } public static double? ParseDouble(this NameValueCollection col, string key) { return Parse(col, key, double.Parse); } private static T? Parse<T>(NameValueCollection col, string key, Func<string, T> parse) where T : struct { string value = col[key]; if (string.IsNullOrEmpty(value)) return default(T?); return parse(value); }

Pero eso arrojaría una excepción para cadenas no nulas o vacías que no son numéricas. Es mejor usar TryParse. Los delegados de Func incorporados no admiten parámetros de ref o out, por lo que tendría que declarar su propio tipo de delegado, pero eso es bastante trivial.


Otra sugerencia para la implementación, utilizando un método TryParse o Parse con un enfoque genérico. Escribí esto originalmente para convertir cadenas analizadas desde un archivo csv en diferentes tipos, int, decimal, list, etc.

public static bool TryParse<T>(this string value, out T newValue, T defaultValue = default(T)) where T : struct, IConvertible { newValue = defaultValue; try { newValue = (T)Convert.ChangeType(value, typeof(T)); } catch { return false; } return true; } public static T Parse<T>(this string value) where T : struct, IConvertible { return (T) Convert.ChangeType(value, typeof (T)); }

Aquí, el método try parse primero establece newValue en el valor predeterminado, luego trata de convertir el valor en type T y devuelve el newValue como tipo T. Si la conversión falla, devuelve el valor predeterminado de T.

El método Parse, simplemente intenta hacer la conversión, sin embargo, si no es a prueba de fallas, arrojará una excepción si la conversión falla.


Añadiré un poco de manera no documentada:

public static T Convert<T>() { if (typeof(T) == typeof(int)) { int a = 5; T value = __refvalue(__makeref(a), T); return value; } else if (typeof(T) == typeof(long)) { long a = 6; T value = __refvalue(__makeref(a), T); return value; } throw new NotImplementedException(); }

Hay poca documentación sobre ellos, pero funcionan a partir de C # 4.0. Lea, por ejemplo, aquí Características ocultas de C #? Recuerde que no documentado significa que no es compatible, bla, bla, bla, no podría funcionar en el futuro, bla, bla, bla, si los usa, el diablo vendrá por usted, bla, bla, bla :-)