Domanda

Voglio usare la riflessione e fare una copertura implicita o esplicita usando la riflessione.

Dato che ho definito foo in 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 eseguo questo codice

decimal someNumber = 42.42m;

var test = (Foo)someNumber;

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

Quando provo a definire una classe con FOO come tipo di membro e utilizzare la riflessione per impostarlo. Ricevo 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 utilizzo 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?

È stato utile?

Soluzione

Beh, non è davvero diverso dal tuo codice non riflettente, è ancora necessario lanciare esplicitamente il numero su a Foo:

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

Esempio dal vivo: http://rexter.com/rundotnet?code=bpq74480

Per interesse ho provato alcune alternative.

  1. Renderlo un implicit gettare Foo - non funziona, stesso errore Abitare
  2. Uso Convert.ChangeType(17.17m,typeof(Foo)) - Inoltre non funziona. Abitare

Altri suggerimenti

Oggi ho guardato questa domanda mentre cercavo di copiare i campi per nome tra gli oggetti. Sono stato piuttosto deluso nel vedere che la risposta selezionata è stata "puoi solo chiamare esplicitamente un operatore esplicito". Dopotutto, qualsiasi altra cosa può essere fatta per riflessione.

Il mio problema era un metodo di riflessione che cercava di fare una copia profonda tra due classi a causa di un tipo complesso. Ho provato a definire un explicit operator conversione, ma non sembrava essere chiamata, quindi ho trovato un modo per ottenerlo riflettendo. Usando alcune altre ricerche sulla chiamata dei metodi statici, ho scoperto che questo funziona per me durante la copia di un tipo complesso memorizzato in Psource in un tipo diverso di proprietà. 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'attuale PropertyInfo nell'istanza di destinazione.

La fonte è la mia istanza di origine. PSource è l'attuale proprietà info nell'istanza di origine.

Il tipo utilizzato per la mia proprietà di destinazione ha una conversione esplicita dal tipo di proprietà di origine, funziona senza necessitarne

Avevo bisogno di funzionalità come Ted H, ma l'ho implementato in questo modo:

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

Modificare: Di recente avevo bisogno di una versione più evoluta, ed è 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();

}

Costruendo su La risposta di Herman... Mi sono reso conto che sia la classe di origine che quella di destinazione possono definire l'operatore di conversione. Quindi ecco 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;
}

Uso tipico:

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

Nota quanto segue:

  • Se la conversione è definita sia nella sorgente che nella destinazione, il metodo dell'operatore di conversione di destinazione viene data la precedenza
  • La funzione restituisce un bool che indica successo/fallimento e il valore effettivo convertito in un out variabile (simile ai metodi Tryparerse)

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

    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;
    }
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top