Trasmissione di una variabile utilizzando una variabile di tipo


281

In C # posso lanciare una variabile di tipo oggetto in una variabile di tipo T dove T è definito in una variabile di tipo?


12
Non strettamente sull'argomento, ma sembri abbastanza confuso su ciò che "cast" significa che potrebbe essere una buona idea capire esattamente quali sono lo scopo e la semantica dell'operatore del cast. Ecco un buon inizio: blogs.msdn.com/ericlippert/archive/2009/03/19/…
Eric Lippert

2
Pensavo di aver escogitato qualcosa. Se hai una Typevariabile, puoi usare la riflessione per creare un'istanza di quel tipo. Quindi è possibile utilizzare un metodo generico per restituire il tipo desiderato deducendolo da un parametro di quel tipo. Sfortunatamente, qualsiasi metodo di riflessione che crea un'istanza di un tipo avrà un tipo di ritorno di object, quindi CastByExampleuserà anche il tuo metodo generico object. Quindi non c'è davvero modo di farlo, e anche se ci fosse, cosa faresti con l'oggetto appena lanciato? Non puoi usare i suoi metodi o altro perché non conosci il suo tipo.
Kyle Delaney,

@KyleDelaney Grazie, sono completamente d'accordo! Come ho cercato di spiegare nella mia risposta, non è davvero utile lanciare qualcosa su qualcosa di diverso senza a un certo punto definire il Tipo che si sta effettivamente utilizzando. L'intero punto dei tipi è il controllo del tipo di tempo del compilatore. Se hai solo bisogno di fare chiamate sull'oggetto, puoi usare objecto dynamic. Se si desidera caricare in modo dinamico moduli esterni, è possibile che le classi condividano un'interfaccia comune e eseguano il cast dell'oggetto. Se non controlli il codice di terze parti, crea piccoli wrapper e implementa l'interfaccia su quello.
Zyphrax,

Risposte:


203

Ecco un esempio di cast e convertito:

using System;

public T CastObject<T>(object input) {   
    return (T) input;   
}

public T ConvertObject<T>(object input) {
    return (T) Convert.ChangeType(input, typeof(T));
}

Modificare:

Alcune persone nei commenti affermano che questa risposta non risponde alla domanda. Ma la linea (T) Convert.ChangeType(input, typeof(T))fornisce la soluzione. Il Convert.ChangeTypemetodo tenta di convertire qualsiasi oggetto nel tipo fornito come secondo argomento.

Per esempio:

Type intType = typeof(Int32);
object value1 = 1000.1;

// Variable value2 is now an int with a value of 1000, the compiler 
// knows the exact type, it is safe to use and you will have autocomplete
int value2 = Convert.ChangeType(value1, intType);

// Variable value3 is now an int with a value of 1000, the compiler
// doesn't know the exact type so it will allow you to call any
// property or method on it, but will crash if it doesn't exist
dynamic value3 = Convert.ChangeType(value1, intType);

Ho scritto la risposta con i generici, perché penso che è molto probabile segno di odore di codice quando si vuole lanciare a somethingai a something elsesenza gestire un tipo effettivo. Con interfacce adeguate che non dovrebbero essere necessarie il 99,9% delle volte. Ci sono forse alcuni casi limite quando si tratta di riflettere che potrebbe avere senso, ma consiglierei di evitarli.

Modifica 2:

Alcuni consigli extra:

  • Cerca di mantenere il tuo codice il più sicuro possibile. Se il compilatore non conosce il tipo, non può verificare se il codice è corretto e cose come il completamento automatico non funzioneranno. Detto semplicemente: se non riesci a prevedere i tipi al momento della compilazione, come potrebbe essere il compilatore ?
  • Se le classi con cui stai lavorando implementano un'interfaccia comune , puoi trasmettere il valore a quell'interfaccia. Altrimenti considera di creare la tua interfaccia e chiedi alle classi di implementare quell'interfaccia.
  • Se stai lavorando con librerie esterne che stai importando in modo dinamico, controlla anche se c'è un'interfaccia comune. In caso contrario, prendere in considerazione la creazione di classi wrapper di piccole dimensioni che implementano l'interfaccia.
  • Se desideri effettuare chiamate sull'oggetto, ma non ti interessa il tipo, archivia il valore in una objecto dynamicvariabile.
  • I generici possono essere un ottimo modo per creare codice riutilizzabile che si applica a molti tipi diversi, senza dover conoscere i tipi esatti coinvolti.
  • Se sei bloccato, considera un approccio o un refactor diverso. Il tuo codice deve essere davvero così dinamico? Deve tenere conto di qualsiasi tipo esiste?

145
Non so come sia questo PO di aiuto. Ha una variabile di tipo, non Tcome tale.
nawfal,

12
@nawfal, in sostanza la linea Convert.ChangeType(input, typeof(T));offre la soluzione. È possibile sostituire facilmente typeof(T)con una variabile di tipo esistente. Una soluzione migliore (se possibile) sarebbe quella di impedire il tipo dinamico tutti insieme.
Zyphrax,

59
@Zyphrax, no richiede ancora un cast per il Tquale non è disponibile.
nawfal,

4
So che l'oggetto risultante è davvero di tipo, Tma comunque ottieni solo objectun riferimento. hmm, ho trovato la domanda interessante nella premessa che OP ha solo la Typevariabile e nessun'altra informazione. Come se la firma del metodo fosse Convert(object source, Type destination):) Tuttavia ottengo il tuo punto
nawfal

10
In che modo questa è una soluzione a questa domanda? Ho lo stesso problema e non ho un <T> generico. Ho solo una variabile di tipo.
Nuri Tasdemir,

114

Altre risposte non menzionano il tipo "dinamico". Pertanto, per aggiungere un'altra risposta, è possibile utilizzare il tipo "dinamico" per archiviare l'oggetto risultante senza dover eseguire il cast di un oggetto convertito con un tipo statico.

dynamic changedObj = Convert.ChangeType(obj, typeVar);
changedObj.Method();

Tieni presente che con l'uso di "dinamico" il compilatore ignora il controllo statico del tipo che potrebbe introdurre possibili errori di runtime se non stai attento.


19
Questa è la risposta corretta Senza la parola chiave dinamica typeof (changedObj) è "oggetto". Con la parola chiave dinamica funziona perfettamente e typeof (changedObject) riflette correttamente lo stesso tipo di typeVar. Inoltre, non è necessario eseguire il cast (T) che non è possibile eseguire se non si conosce il tipo.
rushinge,

5
Ho usato l'eccezione "L'oggetto deve implementare IConvertible" durante l'utilizzo di questa soluzione. Qualsiasi aiuto?
Nuri Tasdemir,

@NuriTasdemir Difficile dirlo, ma credo che la conversione che stai facendo non sia possibile senza IConvertible. Quali sono i tipi coinvolti nella tua conversione?
maulik13,

Mentre questo funziona, c'è una penalità prestazionale nell'uso della dinamica. Sconsigliamo di usarli a meno che tu non stia lavorando con altri runtime (che è ciò per cui sono state progettate le dinamiche).
Bolo,

19

Ecco il mio metodo per lanciare un oggetto ma non in una variabile di tipo generico, piuttosto in modo System.Typedinamico:

Creo un'espressione lambda in fase di runtime usando System.Linq.Expressions, di tipo Func<object, object>, che deseleziona il suo input, esegue la conversione del tipo desiderata e poi dà il risultato in scatola. Ne è necessario uno nuovo non solo per tutti i tipi su cui viene eseguito il cast, ma anche per i tipi su cui viene eseguito il cast (a causa del passaggio di unboxing). La creazione di queste espressioni richiede molto tempo, a causa della riflessione, della compilazione e della costruzione del metodo dinamico che viene eseguita sotto il cofano. Fortunatamente, una volta create, le espressioni possono essere invocate ripetutamente e senza spese generali elevate, quindi memorizzo nella cache ognuna.

private static Func<object, object> MakeCastDelegate(Type from, Type to)
{
    var p = Expression.Parameter(typeof(object)); //do not inline
    return Expression.Lambda<Func<object, object>>(
        Expression.Convert(Expression.ConvertChecked(Expression.Convert(p, from), to), typeof(object)),
        p).Compile();
}

private static readonly Dictionary<Tuple<Type, Type>, Func<object, object>> CastCache
= new Dictionary<Tuple<Type, Type>, Func<object, object>>();

public static Func<object, object> GetCastDelegate(Type from, Type to)
{
    lock (CastCache)
    {
        var key = new Tuple<Type, Type>(from, to);
        Func<object, object> cast_delegate;
        if (!CastCache.TryGetValue(key, out cast_delegate))
        {
            cast_delegate = MakeCastDelegate(from, to);
            CastCache.Add(key, cast_delegate);
        }
        return cast_delegate;
    }
}

public static object Cast(Type t, object o)
{
    return GetCastDelegate(o.GetType(), t).Invoke(o);
}

Nota che questa non è magia. Il cast non avviene nel codice, come accade con la dynamicparola chiave, vengono convertiti solo i dati sottostanti dell'oggetto. Al momento della compilazione siamo ancora lasciati a capire con cura esattamente quale tipo potrebbe essere il nostro oggetto, rendendo questa soluzione poco pratica. Ho scritto questo come un trucco per invocare operatori di conversione definiti da tipi arbitrari, ma forse qualcuno là fuori può trovare un caso d'uso migliore.


2
Richiedeusing System.Linq.Expressions;
Aaron D il

4
Per me questo soffre dello stesso problema della risposta di Zyphrax. Non riesco a invocare metodi sull'oggetto restituito perché è ancora di tipo "oggetto". Sia che io usi il suo metodo ("a" sotto) o il tuo metodo ("b" sotto) ottengo lo stesso errore sul cast (t) - "'t' è una variabile ma è usata come un tipo.Type t = typeof(MyGeneric<>).MakeGenericType(obj.OutputType); var a = (t)Convert.ChangeType(obj, t); var b = (t)Caster.Cast(t, obj);
muusbolla

La risposta originale di @muusbolla Zyphrax usa generici e variabili di tipo, no Type. Non puoi eseguire il cast utilizzando la normale sintassi del cast se tutto ciò che hai è l'oggetto Type. Se si desidera poter utilizzare l'oggetto come un tipo T in fase di compilazione, non in fase di esecuzione, è necessario eseguirne il cast utilizzando una variabile di tipo o solo il nome del tipo effettivo. Puoi fare il primo usando la risposta di Zaphrax.
Ashley

8

Mettendo da parte boxing e unboxing per semplicità, non esiste alcuna azione di runtime specifica coinvolta nel cast lungo la gerarchia dell'ereditarietà. È principalmente una cosa in fase di compilazione. In sostanza, un cast dice al compilatore di trattare il valore della variabile come un altro tipo.

Cosa potresti fare dopo il cast? Non conosci il tipo, quindi non saresti in grado di chiamare alcun metodo su di esso. Non ci sarebbe niente di speciale che potresti fare. In particolare, può essere utile solo se conosci i possibili tipi in fase di compilazione, esegui il cast manualmente e gestisci ciascun caso separatamente con le ifistruzioni:

if (type == typeof(int)) {
    int x = (int)obj;
    DoSomethingWithInt(x);
} else if (type == typeof(string)) {
    string s = (string)obj;
    DoSomethingWithString(s);
} // ...

1
Potresti spiegarlo più chiaramente in relazione alla mia domanda?
theringostarrs

Quello che sto cercando di spiegare è, cosa potresti fare dopo? Non puoi fare molto in quanto il compilatore C # richiede la digitazione statica per poter fare una cosa utile con l'oggetto
Mehrdad Afshari

Hai ragione. Conosco i tipi previsti di due variabili che vengono inviati al metodo come tipo "oggetto". Voglio trasmettere i tipi previsti memorizzati in variabili e aggiungerli alla raccolta. Molto più facile diramare sul tipo e tentare un normale cast e catturare errori.
theringostarrs

4
La tua risposta è buona, ma solo per essere pignoli, noto che i cast non influenzano mai le variabili . Non è mai legale lanciare una variabile su una variabile di un altro tipo; i tipi di variabili sono invarianti in C #. È possibile eseguire il cast del valore memorizzato nella variabile solo su un altro tipo.
Eric Lippert,

L'introduzione della tipizzazione dinamica di C # 4.0 modifica questa risposta?
Daniel T.

6

Come hai potuto farlo? È necessaria una variabile o un campo di tipo T in cui è possibile memorizzare l'oggetto dopo il cast, ma come si può avere tale variabile o campo se si conosce T solo in fase di esecuzione? Quindi no, non è possibile.

Type type = GetSomeType();
Object @object = GetSomeObject();

??? xyz = @object.CastTo(type); // How would you declare the variable?

xyz.??? // What methods, properties, or fields are valid here?

3
Se si utilizza una classe generica, che definisce un metodo con valore di ritorno di tipo T, potrebbe essere necessario farlo. Ad esempio, analizzare una stringa in un'istanza di T e restituirla.
Oliver Friedrich,

7
Questa non è la risposta corretta per fortuna. Vedi la risposta di maulik13.
rushinge,

3
In quale nome di Heaven trovi un CastTometodo Object?
ProfK,

3

Quando si tratta di lanciare sul tipo Enum:

private static Enum GetEnum(Type type, int value)
    {
        if (type.IsEnum)
            if (Enum.IsDefined(type, value))
            {
                return (Enum)Enum.ToObject(type, value);
            }

        return null;
    }

E lo chiamerai così:

var enumValue = GetEnum(typeof(YourEnum), foo);

Questo è stato essenziale per me in caso di ottenere il valore dell'attributo Descrizione di diversi tipi di enum dal valore int:

public enum YourEnum
{
    [Description("Desc1")]
    Val1,
    [Description("Desc2")]
    Val2,
    Val3,
}

public static string GetDescriptionFromEnum(Enum value, bool inherit)
    {
        Type type = value.GetType();

        System.Reflection.MemberInfo[] memInfo = type.GetMember(value.ToString());

        if (memInfo.Length > 0)
        {
            object[] attrs = memInfo[0].GetCustomAttributes(typeof(DescriptionAttribute), inherit);
            if (attrs.Length > 0)
                return ((DescriptionAttribute)attrs[0]).Description;
        }

        return value.ToString();
    }

e poi:

string description = GetDescriptionFromEnum(GetEnum(typeof(YourEnum), foo));
string description2 = GetDescriptionFromEnum(GetEnum(typeof(YourEnum2), foo2));
string description3 = GetDescriptionFromEnum(GetEnum(typeof(YourEnum3), foo3));

In alternativa (approccio migliore), tale casting potrebbe apparire così:

 private static T GetEnum<T>(int v) where T : struct, IConvertible
    {
        if (typeof(T).IsEnum)
            if (Enum.IsDefined(typeof(T), v))
            {
                return (T)Enum.ToObject(typeof(T), v);
            }

        throw new ArgumentException(string.Format("{0} is not a valid value of {1}", v, typeof(T).Name));
    }

1

Dopo aver trovato nulla per aggirare l'eccezione "L'oggetto deve implementare IConvertible" quando si utilizza la risposta di Zyphrax (tranne per l'implementazione dell'interfaccia) .. Ho provato qualcosa di non convenzionale e ho lavorato per la mia situazione.

Utilizzo del pacchetto nuget Newtonsoft.Json ...

var castedObject = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(myObject), myType);

1

Danno, il problema è che non hai un T.

hai solo una variabile Type.

Un suggerimento a MS, se potessi fare qualcosa del genere

TryCast<typeof(MyClass)>

se risolverebbe tutti i nostri problemi.


0

Non capirò mai perché hai bisogno di una reputazione fino a 50 per lasciare un commento, ma dovevo solo dire che la risposta di @Curt è esattamente quello che stavo cercando e spero che qualcun altro.

Nel mio esempio, ho un ActionFilterAttribute che stavo usando per aggiornare i valori di un documento patch json. Non ho fatto quello che era il modello T per il documento patch per cui ho dovuto serializzare e deserializzare su un semplice documento JsonPatch, modificarlo, quindi perché avevo il tipo, serializzare e deserializzare nuovamente il tipo.

Type originalType = //someType that gets passed in to my constructor.

var objectAsString = JsonConvert.SerializeObject(myObjectWithAGenericType);
var plainPatchDocument = JsonConvert.DeserializeObject<JsonPatchDocument>(objectAsString);

var plainPatchDocumentAsString= JsonConvert.SerializeObject(plainPatchDocument);
var modifiedObjectWithGenericType = JsonConvert.DeserializeObject(plainPatchDocumentAsString, originalType );

-1
public bool TryCast<T>(ref T t, object o)
{
    if (
        o == null
        || !typeof(T).IsAssignableFrom(o.GetType())
        )
        return false;
    t = (T)o;
    return true;
}

2
Potresti per favore sottolineare come questa risposta differisce dalle altre risposte e dove questa soluzione è appropriata?
Klaus Gütter,

-2

ancora più pulito:

    public static bool TryCast<T>(ref T t, object o)
    {
        if (!(o is T))
        {
            return false;
        }

        t = (T)o;
        return true;
    }

-2

Se è necessario eseguire il cast di oggetti in fase di esecuzione senza conoscere il tipo di destinazione, è possibile utilizzare la riflessione per creare un convertitore dinamico.

Questa è una versione semplificata (senza metodo generato nella cache):

    public static class Tool
    {
            public static object CastTo<T>(object value) where T : class
            {
                return value as T;
            }

            private static readonly MethodInfo CastToInfo = typeof (Tool).GetMethod("CastTo");

            public static object DynamicCast(object source, Type targetType)
            {
                return CastToInfo.MakeGenericMethod(new[] { targetType }).Invoke(null, new[] { source });
            }
    }

allora puoi chiamarlo:

    var r = Tool.DynamicCast(myinstance, typeof (MyClass));
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.