2011-09-08 14 views
10

Voglio usare la riflessione e fare una cover implicita o esplicita usando la riflessione.Come si esegue il casting di un'operazione esplicita dalla riflessione?

Dato che hanno definito Foo questo modo

public class Foo 
{ 
    public static explicit operator decimal(Foo foo) 
    { 
     return foo.Value; 
    } 

    public static explicit operator Foo(decimal number) 
    { 
     return new Foo(number); 
    } 

    public Foo() { } 

    public Foo(decimal number) 
    { 
     Value = number; 
    } 

    public decimal Value { get; set; } 

    public override string ToString() 
    { 
     return Value.ToString(); 
    } 
} 

Quando si esegue questo codice

decimal someNumber = 42.42m; 

var test = (Foo)someNumber; 

Console.WriteLine(test);  // Writes 42.42 No problems 

Quando si tenta di definire una classe con Foo come Tipo di utente e utilizzare la riflessione per impostarlo. Ottengo la seguente eccezione.

Error  : Object of type 'System.Decimal' cannot be converted to type 'Foo'. 
StackTrace: at System.RuntimeType.TryChangeType(Object value, Binder binder, CultureInfo culture, Boolean needsSpecialCast) 
       at System.RuntimeType.CheckValue(Object value, Binder binder, CultureInfo culture, BindingFlags invokeAttr) 
       at System.Reflection.MethodBase.CheckArguments(Object[] parameters, Binder binder, BindingFlags invokeAttr, CultureInfo culture, Signature sig) 
       at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture, Boolean skipVisibilityChecks) 
       at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture) 
       at System.Reflection.RuntimePropertyInfo.SetValue(Object obj, Object value, BindingFlags invokeAttr, Binder binder, Object[] index, CultureInfo culture) 
       at System.Reflection.RuntimePropertyInfo.SetValue(Object obj, Object value, Object[] index) 

Ecco il codice che uso per impostare la proprietà con la riflessione

public class FooComposite 
{ 
    public Foo Bar { get; set; } 
} 

var properties = typeof(FooComposite).GetProperties(); 

var testFoo = new FooComposite(); 

foreach(var propertyInfo in properties) 
{ 
    propertyInfo.SetValue(testFoo, 17.17m, null); // Exception generated on this line 
} 

Console.WriteLine(testFoo.Bar); // Never gets here 

Come posso fare questa conversione?

+0

Se si vogliono fare questo in modo dinamico e non codificare il cast, Marc ha fatto qualcosa di molto vicino a quello che si sta cercando per oltre il forum MSDN utilizzando espressioni (leggi tutta la discussione, non solo la risposta). Vedi: http://social.msdn.microsoft.com/Forums/eu/csharplanguage/thread/c2a77a57-ebbb-4ac1-94c9-5287f01105ff – vcsjones

risposta

5

Bene la sua realtà non è diverso dal codice non-riflessione, è ancora necessario per lanciare in modo esplicito il numero a un Foo:

propertyInfo.SetValue(testFoo,(Foo)17.17m, null); 

vivo esempio: http://rextester.com/rundotnet?code=BPQ74480

Fuori di interesse ho provato un paio di alternative.

  1. rendono un implicit cast Foo - non funziona, lo stesso errore Live
  2. Usa Convert.ChangeType(17.17m,typeof(Foo)) - anche non funziona. Live
+0

Questo è un buon post sul blog che parla di questo problema di boxe e unboxing. http://philosopherdeveloper.wordpress.com/2010/05/05/the-difference-between-converting-and-unboxing-read-my-mind-compiler/ –

4

ho guardato a questa domanda oggi, durante il tentativo di copiare i campi per nome tra gli oggetti. Sono rimasto piuttosto deluso nel vedere che la risposta selezionata era "puoi solo chiamare esplicitamente un operatore esplicito". Dopotutto, qualsiasi altra cosa può essere fatta con la riflessione.

Il mio problema era un metodo di riflessione che cercava di eseguire una copia profonda tra due classi a causa di un tipo complesso. Ho provato a definire una conversione explicit operator, ma non sembra essere stata chiamata, quindi ho trovato un modo per ottenerlo per riflessione. Usando qualche altra ricerca sulla chiamata di metodi statici, ho trovato che questo funziona per me quando si copia un tipo complesso memorizzato in pSource in un tipo diverso nella proprietà pDest. il tipo in pDest ha una conversione dal tipo di pSource.


MethodInfo[] static_methods = pDest.PropertyType.GetMethods(System.Reflection.BindingFlags.Static | BindingFlags.Public); 
if (static_methods != null) 
{ 
    foreach (MethodInfo method in static_methods) 
    { 
     if(method.Name== "op_Explicit")      // this is a constant 
     {              // for explicit operators 
      ParameterInfo[] paramSet = method.GetParameters(); 
      if ((paramSet != null) && (paramSet.Length == 1)) 
      { 
       if (paramSet[0].ParameterType == pSource.PropertyType) // match the types! 
       { 
        pDest.SetValue(       // Destination prop 
         dstVar,        // Destination instance 
         method.Invoke(      // converter method 
           null,       // static has no 'this' 
           new object[] {     // value to convert from 
            pSource.GetValue(source, null) 
           }        // source property on 
                  // source instance 
         ) 
        ); // SetValue(...) 
       } 
      } 
     } 
    } 
} 

dstVar è la mia istanza di destinazione. pDest è l'oggetto PropertyInfo corrente nell'istanza di destinazione.

l'origine è la mia istanza di origine. pSource è l'oggetto PropertyInfo corrente nell'istanza di origine.

del tipo usato per la mia proprietà destinazione ha una conversione esplicita dal tipo di proprietà source, questo funziona senza bisogno di alcun

4

avevo bisogno di funzionalità come Ted H, ma ho implementato in questo modo:

var cast = typeof(dest).GetMethod("op_Explicit", new Type[] { typeof(source) }); 
var result = cast.Invoke(null, new object[] {value}); 

Modifica: Avevo bisogno di una versione più evoluta di recente, e questo è quello che mi è venuto in mente. Tieni presente che non copre tutte le conversioni disponibili.

private static object DynamicCast(object source, Type destType) { 
    Type srcType = source.GetType(); 
    if (srcType == destType) return source; 

    var paramTypes = new Type[] { srcType }; 
    MethodInfo cast = destType.GetMethod("op_Implicit", paramTypes); 

    if (cast == null) { 
     cast = destType.GetMethod("op_Explicit", paramTypes); 
    } 

    if (cast != null) return cast.Invoke(null, new object[] { source }); 

    if (destType.IsEnum) return Enum.ToObject(destType, source); 

    throw new InvalidCastException(); 

} 
+0

Nota che sia i tipi di origine che di destinazione possono consentire la conversione operatori. Ad esempio, se hai una conversione esplicita da A a B, allora questo può essere definito in classe A o in classe B, o entrambi. –

+0

Penso che potrei averlo implementato così intenzionalmente. Forse in modo che corrisponda al modo in cui funziona nel codice C# statico, ma non ne sono sicuro. Se vuoi veramente fare conversioni estese potresti anche voler esaminare la classe 'Convert' e' TypeConverter'. – Herman

1

Sulla Herman's answer ... ho capito che sia l'origine e la classe di destinazione possono definire l'operatore di conversione. Così qui è la mia versione:

private static bool DynamicCast(object source, Type destType, out object result) 
{ 
    Type srcType = source.GetType(); 
    if (srcType == destType) { result = source; return true; } 
    result = null; 

    BindingFlags bf = BindingFlags.Static | BindingFlags.Public; 
    MethodInfo castOperator = destType.GetMethods(bf) 
           .Union(srcType.GetMethods(bf)) 
           .Where(mi => mi.Name == "op_Explicit" || mi.Name == "op_Implicit") 
           .Where(mi => 
           { 
            var pars = mi.GetParameters(); 
            return pars.Length == 1 && pars[0].ParameterType == srcType; 
           }) 
           .Where(mi => mi.ReturnType == destType) 
           .FirstOrDefault(); 
    if (castOperator != null) result = castOperator.Invoke(null, new object[] { source }); 
    else return false; 
    return true; 
} 

utilizzo tipico:

object a = new A(); 
object o; 
if (DynamicCast(a, typeof(B), out o)) 
{ 
    B b = (B)o; 
    ... 
} 

notare quanto segue:

  • Se la conversione è definita sia in origine e di destinazione, il metodo di destinazione di operatore di conversione è data precedenza
  • La funzione restituisce un valore boolea che indica successo/fallimento e il valore effettivo convertito in unVariabile(simile ai metodi TryParse)
0

Grazie a tutti per un ottimo inizio su ciò di cui avevo bisogno. Ho preso in prestito e ho anche aggiunto. Nella mia situazione, avevo bisogno di tutto quanto sopra e avevo anche bisogno di cercare tutti i tipi di base degli antenati sia per il tipo di origine che di destinazione per vedere se qualcuno di essi contenesse una conversione implicita o esplicita ai miei tipi di destinazione. Aggiungendo questo requisito extra ho prodotto il seguito.

private static bool TryCast(object source, Type destType, out object result) 
    { 
     Type srcType = source.GetType(); 
     if (srcType == destType) 
     { 
      result = source; 
      return true; 
     } 

     MethodInfo cast = null; 
     while (cast == null && srcType != typeof(object)) 
     { 
      cast = GetCastMethod(srcType, srcType, destType); 
      if (cast == null) cast = GetCastMethod(destType, srcType, destType); 
      srcType = srcType.BaseType; 
     } 

     if (cast != null) 
     { 
      result = cast.Invoke(null, new object[] { source }); 
      return true; 
     } 

     if (destType.IsEnum) 
     { 
      result = Enum.ToObject(destType, source); 
      return true; 
     } 

     result = null; 
     return false; 
    } 

    private static MethodInfo GetCastMethod(Type typeWithMethod, Type srcType, Type destType) 
    { 
     while (typeWithMethod != typeof(object)) 
     { 
      foreach (MethodInfo method in typeWithMethod.GetMethods(BindingFlags.Static | BindingFlags.Public)) 
      { 
       if (method.ReturnType == destType && (method.Name == "op_Explicit" || method.Name == "op_Implicit")) 
       { 
        ParameterInfo[] parms = method.GetParameters(); 
        if (parms != null && parms.Length == 1 && parms[0].ParameterType == srcType) 
         return method; 
       } 
      } 
      typeWithMethod = typeWithMethod.BaseType; 
     } 

     return null; 
    }