2010-02-08 10 views
21

Dado Tipo a y Tipo b, ¿cómo puedo, en tiempo de ejecución, determinar si hay una conversión implícita de a a b?Cómo saber si el Tipo A es implícitamente convertible en Tipo B

Si eso no tiene sentido, consideremos el siguiente método:

public PropertyInfo GetCompatibleProperty<T>(object instance, string propertyName) 
{ 
    var property = instance.GetType().GetProperty(propertyName); 

    bool isCompatibleProperty = !property.PropertyType.IsAssignableFrom(typeof(T)); 
    if (!isCompatibleProperty) throw new Exception("OH NOES!!!"); 

    return property; 
} 

Y aquí está el código de llamada que quiero trabajar:

// Since string.Length is an int property, and ints are convertible 
// to double, this should work, but it doesn't. :-(
var property = GetCompatibleProperty<double>("someStringHere", "Length"); 

Respuesta

24

Tenga en cuenta que IsAssignableFrom no resuelve su problema . Tienes que usar Reflection como tal. Tenga en cuenta la necesidad explícita de manejar los tipos primitivos; estas listas son por §6.1.2 (conversiones numéricas implícitas) de la especificación.

static class TypeExtensions { 
    static Dictionary<Type, List<Type>> dict = new Dictionary<Type, List<Type>>() { 
     { typeof(decimal), new List<Type> { typeof(sbyte), typeof(byte), typeof(short), typeof(ushort), typeof(int), typeof(uint), typeof(long), typeof(ulong), typeof(char) } }, 
     { typeof(double), new List<Type> { typeof(sbyte), typeof(byte), typeof(short), typeof(ushort), typeof(int), typeof(uint), typeof(long), typeof(ulong), typeof(char), typeof(float) } }, 
     { typeof(float), new List<Type> { typeof(sbyte), typeof(byte), typeof(short), typeof(ushort), typeof(int), typeof(uint), typeof(long), typeof(ulong), typeof(char), typeof(float) } }, 
     { typeof(ulong), new List<Type> { typeof(byte), typeof(ushort), typeof(uint), typeof(char) } }, 
     { typeof(long), new List<Type> { typeof(sbyte), typeof(byte), typeof(short), typeof(ushort), typeof(int), typeof(uint), typeof(char) } }, 
     { typeof(uint), new List<Type> { typeof(byte), typeof(ushort), typeof(char) } }, 
     { typeof(int), new List<Type> { typeof(sbyte), typeof(byte), typeof(short), typeof(ushort), typeof(char) } }, 
     { typeof(ushort), new List<Type> { typeof(byte), typeof(char) } }, 
     { typeof(short), new List<Type> { typeof(byte) } } 
    }; 
    public static bool IsCastableTo(this Type from, Type to) { 
     if (to.IsAssignableFrom(from)) { 
      return true; 
     } 
     if (dict.ContainsKey(to) && dict[to].Contains(from)) { 
      return true; 
     } 
     bool castable = from.GetMethods(BindingFlags.Public | BindingFlags.Static) 
         .Any( 
          m => m.ReturnType == to && 
          (m.Name == "op_Implicit" || 
          m.Name == "op_Explicit") 
         ); 
     return castable; 
    } 
} 

Uso:

bool b = typeof(A).IsCastableTo(typeof(B)); 
+0

Esto nos dirá que tiene un método de conversión implícito o explícito. No si se puede convertir implícitamente entre tipos específicos. –

+0

Eso está bien, Sam, esto funcionará para mi problema. Estoy un poco sorprendido de que no haya una forma incorporada de hacer esto. –

+2

¿Por qué no usar enumerable Any extension? –

5

conversiones implícitas que tendrá que tener en cuenta:

  • Identidad
  • sbyte a short, int, long, float, double o decimal
  • byte a corto, ushort, int, uint, largo, ulong, flotante, doble o decimal
  • corto a int, long, float, doble o decimal
  • ushort a int, uint, largo, ulong, flotador, doble o decimal
  • int a long, float, doble o decimal
  • uint a largo, ulong, flotador, doble o decimal
  • largo de flotar, doble o decimal
  • ulong a flotar, doble, o decimal
  • char a ushort, int, uint, largo, ulong, float, doble o decimal
  • flotante a doble
  • tipo Anulable conversión de tipo
  • referencia al objeto
  • clase derivada de la clase base
  • clase a la interfaz implementada
  • interfaz a la interfaz de base de
  • Array para matriz cuando matrices tienen el mismo número de dimensiones, hay es una conversión implícita del tipo de elemento de origen al tipo de elemento de destino y el tipo de elemento de origen y el tipo de elemento de destino son los tipos de referencia
  • Tipo de matriz a System.Array
  • tipo
  • Array para IList <> y sus interfaces base tipo
  • Delegado a System.Delegate
  • Boxing conversión
  • tipo Enum a System.Enum
  • conversión definido
  • de usuario (op_implicit)

Supongo que estás buscando lo último. Tendrá que escribir algo parecido a un compilador para cubrir todos ellos.Notable es que System.Linq.Expressions.Expression no intentó esta hazaña.

+0

Heh. Interesante. Estoy realmente sorprendido de que no haya forma de decir "este tipo se puede convertir a este otro tipo". –

+0

"Matriz a matriz cuando las matrices tienen la misma longitud y el elemento tiene una conversión implícita" ¿Está seguro? No lo creo. De hecho, no creo que haya una conversión explícita. En cuanto al resto, creo que mi método los cubre a todos. Por lo tanto, debo estar malinterpretando lo que quiere decir con "tendrá que escribir algo parecido a un compilador para cubrir todos". – jason

+0

Sí, estoy seguro. Derived [] es implícitamente convertible a Base []. –

3

La respuesta aceptada a esta pregunta maneja muchos casos, pero no todos. Por ejemplo, aquí son sólo algunos yesos/conversiones válidas que no se manejan correctamente:

// explicit 
var a = (byte)2; 
var b = (decimal?)2M; 

// implicit 
double? c = (byte)2; 
decimal? d = 4L; 

A continuación, he publicado una versión alternativa de esta función que responde específicamente a la cuestión de las conversiones implícitas y conversiones. Para obtener más detalles, el conjunto de pruebas que utilicé para verificarlo y la versión EXPLICIT, consulte my post on the subject.

public static bool IsImplicitlyCastableTo(this Type from, Type to) 
{ 
    // from http://www.codeducky.org/10-utilities-c-developers-should-know-part-one/ 
    Throw.IfNull(from, "from"); 
    Throw.IfNull(to, "to"); 

    // not strictly necessary, but speeds things up 
    if (to.IsAssignableFrom(from)) 
    { 
     return true; 
    } 

    try 
    { 
     // overload of GetMethod() from http://www.codeducky.org/10-utilities-c-developers-should-know-part-two/ 
     // that takes Expression<Action> 
     ReflectionHelpers.GetMethod(() => AttemptImplicitCast<object, object>()) 
      .GetGenericMethodDefinition() 
      .MakeGenericMethod(from, to) 
      .Invoke(null, new object[0]); 
     return true; 
    } 
    catch (TargetInvocationException ex) 
    { 
     return = !(
      ex.InnerException is RuntimeBinderException 
      // if the code runs in an environment where this message is localized, we could attempt a known failure first and base the regex on it's message 
      && Regex.IsMatch(ex.InnerException.Message, @"^The best overloaded method match for 'System.Collections.Generic.List<.*>.Add(.*)' has some invalid arguments$") 
     ); 
    } 
} 

private static void AttemptImplicitCast<TFrom, TTo>() 
{ 
    // based on the IL produced by: 
    // dynamic list = new List<TTo>(); 
    // list.Add(default(TFrom)); 
    // We can't use the above code because it will mimic a cast in a generic method 
    // which doesn't have the same semantics as a cast in a non-generic method 

    var list = new List<TTo>(capacity: 1); 
    var binder = Microsoft.CSharp.RuntimeBinder.Binder.InvokeMember(
     flags: CSharpBinderFlags.ResultDiscarded, 
     name: "Add", 
     typeArguments: null, 
     context: typeof(TypeHelpers), // the current type 
     argumentInfo: new[] 
     { 
      CSharpArgumentInfo.Create(flags: CSharpArgumentInfoFlags.None, name: null), 
      CSharpArgumentInfo.Create(
       flags: CSharpArgumentInfoFlags.UseCompileTimeType, 
       name: null 
      ), 
     } 
    ); 
    var callSite = CallSite<Action<CallSite, object, TFrom>>.Create(binder); 
    callSite.Target.Invoke(callSite, list, default(TFrom)); 
} 
Cuestiones relacionadas