2010-02-08 4 views
21

Étant donné le type a et le type b, comment puis-je, au moment de l'exécution, déterminer s'il existe une conversion implicite de a à b?Comment savoir si le type A est implicitement convertible en type B

Si cela n'a pas de sens, considérer la méthode suivante:

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; 
} 

Et voici le code d'appel que je veux travailler:

// 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"); 

Répondre

24

Notez que IsAssignableFrom ne résout pas votre problème . Vous devez utiliser Reflection comme ça. Notez le besoin explicite de gérer les types primitifs; ces listes sont conformes au § 6.1.1 (Conversions numériques implicites) de la spécification.

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; 
    } 
} 

Utilisation:

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

Cela nous indiquera qu'il a une méthode de conversion implicite ou explicite. Pas si elle peut être implicitement convertie entre des types spécifiques. –

+0

C'est bon, Sam, ça marchera pour mon problème. Je suis un peu surpris qu'il n'y ait pas de façon intégrée de le faire. –

+2

Pourquoi ne pas utiliser l'extension Enumerable? –

5

conversions implicites vous devez considérer:

  • Identité
  • sbyte à short, int, long, float, double ou
  • décimal
  • octet à court, ushort, int, uint, long, ulong, flotteur, double ou décimal
  • court int, long, float, double, ou décimal
  • ushort à int, uint, long, ulong, float, double ou décimal
  • int à long, float, double, ou décimal
  • uint à long, ulong, float, double ou décimal
  • à long flotter, double, ou décimal à flotter
  • ulong, double, ou décimal
  • char USHORT, int, uint, long, ulong, flotteur, double ou décimal
  • float to double
  • conversion type Nullable
  • type de référence à l'objet
  • classe dérivée à la classe de base
  • classe à interface implémentée
  • Interface à l'interface de base
  • Array pour tableau lorsque les tableaux ont le même nombre de dimensions, il est une conversion implicite du type d'élément source au type d'élément de destination et le type d'élément source et le type d'élément de destination sont des types de référence
  • Type de tableau à System.Array
  • type tableau à IList <> et ses interfaces de base
  • type délégué à System.Delegate
  • conversion de boxe
  • de type Enum à System.Enum
  • conversion défini par l'utilisateur (op_implicit)

Je suppose que vous cherchez le dernier. Vous devrez écrire quelque chose ressemblant à un compilateur pour les couvrir tous.Notable est que System.Linq.Expressions.Expression n'a pas tenté cet exploit.

+0

Heh. Intéressant. Je suis vraiment surpris qu'il n'y ait aucune façon de dire, "Ce type peut être converti en cet autre type". –

+0

"Tableau à tableau lorsque les tableaux ont la même longueur et que l'élément a une conversion implicite" Etes-vous sûr? Je ne pense pas. En fait, je ne pense pas qu'il y ait une conversion explicite. Pour le reste, je pense que ma méthode les couvre tous. Donc, je dois mal comprendre ce que vous voulez dire par "vous aurez besoin d'écrire quelque chose qui ressemble à un compilateur pour les couvrir tous". – jason

+0

Oui, je suis sûr. Derived [] est implicitement convertible en Base []. –

3

La réponse acceptée à cette question concerne de nombreux cas, mais pas tous. Par exemple, voici quelques moulages valides/conversions qui ne sont pas correctement:

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

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

Ci-dessous, j'ai posté une version alternative de cette fonction qui répond spécifiquement la question des moulages et des conversions IMPLICIT. Pour plus de détails, la suite de tests que j'ai utilisée pour le vérifier, et la version de distribution EXPLICIT, veuillez consulter 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)); 
}