2009-05-13 10 views
14

tengo una interfaz para una propiedad de mapa chirriante:casting valor de T en un método genérico

interface IPropertyMap 
{ 
    bool Exists(string key); 
    int GetInt(string key); 
    string GetString(string key); 
    //etc.. 
} 

que quieren crear un método de extensión de este modo:

public static T GetOrDefault<T>(this IPropertyMap map, string key, T defaultValue) 
{ 
    if (!map.Exists(key)) 
     return defaultValue; 
    else 
    { 
     if (typeof(T) == typeof(int)) return (T)map.GetInt(key); 
     //etc.. 
    } 
} 

Pero el won compilador No me dejes enviar a T. Intenté agregar where T : struct, pero eso no parece ayudar.

¿Qué me estoy perdiendo?

+0

Supongo que es un error tipográfico, pero todos los métodos en su interfaz devuelven bool ...? –

+0

Sí, copiar y pegar es mi amigo peligroso ... – Benjol

Respuesta

27

Creo que esto se debe a que el compilador no sabe qué tipo de operación necesita realizar. IIRC, se puede conseguir que funcione si se introduce el boxeo:

if (typeof(T) == typeof(int)) return (T)(object)map.GetInt(key); 

pero eso no es ideal en términos de rendimiento.

Creo que es solo una limitación de los genéricos, desafortunadamente.

+1

No estoy recurriendo a este, por lo que dudo que el rendimiento sea un problema. – Benjol

+0

Acabo de hacer una pequeña prueba de GetOrDefault contra GetInt, la diferencia es bastante dramática, para un millón de iteraciones, pero aún insignificante para mis necesidades actuales. – Benjol

3

¿Qué GetInt, GetString hacer internamente? Puede haber otras opciones que implican Convert.ChangeType(...) o TypeDescriptor.GetConverter(...).ConvertFrom(...), y un único molde, utilizando un "objeto" paso a paso:

por ejemplo, si los objetos son ya bien proporcionado:

public T GetOrDefault<T>(this IPropertyMap map, string key, T defaultValue) 
{ 
    return map.Exists(key) ? (T)map[key] : defaultValue; 
} 

o si se almacenan como cuerdas y necesita la conversión, algo que implica:

T typedVal = (T) TypeDescriptor.GetConverter(typeof(T)).ConvertFrom(map[key]); 
+0

Bueno, no sé porque no tengo acceso a todas las implementaciones de la interfaz. ¿Hay alguna diferencia al usar el tipo de cambio (que devuelve el objeto) al boxeo como Jon sugirió? – Benjol

+0

Ver edición, pero si no controla la interfaz no es una opción, espero ... –

3

supongo que es sólo un error tipográfico, pero bool GetInt(string key) parece raro. Debe ser int GetInt(string key), o mejor aún int GetInt32(string key).

A continuación, Jon ya ha notado que el boxeo es necesario para que su código funcione, por lo que esto es lo que debe hacer.

Y, por último, añadir un método de "catch-all" a su interfaz IPropertyMap - decir object GetValue(string key) y vuelva a grabar GetOrDefault<T> para utilizar este método en lugar de un sinfín de error y Type comparaciones con tendencia:

else 
    return (T)(object)map.GetValue(key);  
+0

Agradable, no funciona para la interfaz que mencioné, pero acabo de descubrir otra que * no * tener GetAsObject(). – Benjol

0

Sólo como referencia , descubrí otra interfaz que tiene GetType() y GetAsObject() métodos, lo que me permite integrar elementos de estas respuestas para hacer esto:

public static T GetOrDefault<T>(this IInfosContainer container, string key, T defaultValue) 
{ 
    //I just read p273 of C# in Depth, +1 Jon Skeet :) 
    if (container == null) throw new ArgumentNullException("container"); 
    if (container.Exist(key)) 
    { 
     if (container.GetType(key) != typeof(T)) 
      throw new ArgumentOutOfRangeException("key", 
       "Key exists, but not same type as defaultValue parameter"); 
     else 
      return (T)container.GetAsObject(key); 
    } 
    else 
     return defaultValue; 
} 

(Los puristas notarán que no soy de la escuela 'llaves para una declaración' ...)

0

No creo que este sea un buen método. No tienes forma de controlar lo que es T. Por ejemplo

float value = map.GetOrDefault ("blah", 2.0);

no compilará tampoco porque no se puede convertir implícitamente el tipo 'doble' en 'flotante'. Existe una conversión explícita (¿falta un molde?) Otro punto de falla es cuando el valor predeterminado deseado es nulo.Este método deja al desarrollador a merced del compilador para resolver lo que cree que el desarrollador pretende.

Si puede cambiar la interfaz, agregue un método GetObject. Dado que estás utilizando un método de extensión, supongo que no puedes convertirlo a un objeto y luego a int. De cualquier manera cambiar el método para parecerse

pública GetOrDefault static void (este mapa IPropertyMap, clave de cadena, ref T valor) {if ( map.Exists (clave)) { si (typeof (T) = = typeof (int)) { valor = (T) (objeto) map.GetInt (clave); } value = default (T); // esto es simplemente una maravilla porque soy flojo, // agrego código real aquí. }} y llame como esto

 PropertyMap map = new PropertyMap(); 
     float value = 2.0f; 
     map.GetOrDefault("blah", ref value); 

odio params árbitro pero veo el punto aquí. El registro es un ejemplo clásico de cuándo este tipo de método es útil. El código anterior obliga al usuario dev a especificar explícitamente el tipo de salida y conserva el valor predeterminado del concepto.

+0

Sí, vi el problema nulo, actualmente solo estoy usando esto para las estructuras. Sin embargo, creo que nada le impide incluir el parámetro de tipo explícitamente para evitar esta limitación. map.GetOrDefault ("dibble", nulo); – Benjol

Cuestiones relacionadas