2010-02-08 6 views
21

Bei Typ a und Typ b, wie kann ich zur Laufzeit feststellen, ob es eine implizite Konvertierung von a nach b gibt?Wie kann man feststellen, ob Typ A implizit in Typ B konvertierbar ist?

Wenn das nicht Sinn machen, sollten Sie die folgende Methode:

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

Und hier ist der anrufende Code, den ich arbeiten will:

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

Antwort

24

Beachten Sie, dass IsAssignableFrom Ihr Problem nicht lösen . Sie müssen Reflection so verwenden. Beachten Sie die explizite Notwendigkeit, die primitiven Typen zu behandeln; Diese Listen sind gemäß §6.1.2 (Implizite numerische Konvertierungen) der Spezifikation.

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

Verbrauch:

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

Dies wird uns sagen, dass es eine implizite oder explizite Konvertierungsmethode hat. Nicht, ob es implizit zwischen bestimmten Typen konvertiert werden kann. –

+0

Das ist in Ordnung, Sam, das wird für mein Problem funktionieren. Ich bin ein wenig überrascht, dass es keine eingebaute Möglichkeit dafür gibt. –

+2

Warum nicht die aufzählbare Any Erweiterung verwenden? –

5

Implizite Konvertierungen Sie berücksichtigen müssen:

  • Identität
  • sbyte zu kurz, int, long, float, double oder decimal
  • byte zu kurz, ushort, int, uint, lang, ulong, float, double oder decimal
  • kurz in int, lang, float, double, oder dezimale
  • ushort int, uint, lang, ULONG, float, double, oder dezimale
  • int in long, float, double, oder dezimale
  • uint zu lang, ULONG, float, double, oder dezimale
  • lang float, double oder dezimale
  • ULONG float, double oder dezimale
  • char ushort, int, uint, lang, ULONG, float, doppelt oder dezimal
  • float zu double
  • Nullable Typkonvertierung
  • Referenztyp
  • abgeleitete Klasse Basisklasse
  • Klasse implementierte Schnittstelle
  • Schnittstelle zum Basisschnittstelle
  • Array zu Array-Objekt, wenn Arrays die gleiche Anzahl von Dimensionen hat, gibt ist eine implizite Konvertierung vom Quellelementtyp in den Zielelementtyp und der Quellelementtyp und der Zielelementtyp sind Referenztypen
  • Array-Typ zu System.Array
  • Array Typ IList <> und seine Basis-Schnittstellen
  • Delegattyp
  • Boxen Umwandlung
  • Enum Typ System.Enum
  • Benutzerdefinierte Konvertierung (op_implicit)

System.Delegate Ich nehme an, du suchst nach letzterem. Sie müssen etwas schreiben, das einem Compiler ähnelt, um alle abzudecken.Bemerkenswert ist, dass System.Linq.Expressions.Expression dieses Feature nicht versucht hat.

+0

Heh. Interessant. Ich bin wirklich überrascht, dass es keine eingebackene Art zu sagen gibt: "Dieser Typ kann in diesen anderen Typ konvertiert werden". –

+0

"Array zu Array, wenn Arrays die gleiche Länge haben und das Element eine implizite Konvertierung hat" Sind Sie sicher? Ich denke nicht. Tatsächlich glaube ich nicht, dass es eine explizite Konvertierung gibt. Im übrigen denke ich, dass meine Methode alle abdeckt. Daher muss ich falsch verstehen, was Sie meinen, "Sie müssen etwas schreiben, das an einen Compiler erinnert, um alle abzudecken." – jason

+0

Ja, da bin ich mir sicher. Derived [] ist implizit in Base [] konvertierbar. –

3

Die akzeptierte Antwort auf diese Frage behandelt viele Fälle, aber nicht alle. Zum Beispiel, hier sind nur einige gültige Würfe/Umwandlungen, die nicht richtig behandelt werden:

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

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

Unten habe ich eine alternative Version dieser Funktion geschrieben, die spezifisch die Frage impliziten Abgüsse und Conversions beantwortet. Für weitere Details, die Testsuite, die ich verwendet habe, um es zu verifizieren, und die EXPLICIT-Cast-Version, sehen Sie sich bitte my post on the subject an.

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