Casting d'une variable à l'aide d'une variable Type

281

En C #, puis-je convertir une variable de type objet en une variable de type T où T est défini dans une variable Type?

theringostarrs
la source
12
Pas strictement sur le sujet, mais vous semblez assez flou sur ce que signifie «cast», ce pourrait être une bonne idée de comprendre précisément le but et la sémantique de l'opérateur de cast. Voici un bon début: blogs.msdn.com/ericlippert/archive/2009/03/19/…
Eric Lippert
2
Je pensais avoir trouvé quelque chose. Si vous avez une Typevariable, vous pouvez utiliser la réflexion pour créer une instance de ce type. Et puis, vous pouvez utiliser une méthode générique pour renvoyer le type souhaité en le déduisant d'un paramètre de ce type. Malheureusement, toute méthode de réflexion qui crée une instance d'un type aura un type de retour de object, donc votre CastByExampleméthode générique sera également utilisée object. Il n'y a donc vraiment aucun moyen de le faire, et même s'il y en avait, que feriez-vous avec l'objet nouvellement casté? Vous ne pouvez pas utiliser ses méthodes ou quoi que ce soit parce que vous ne connaissez pas son type.
Kyle Delaney
@KyleDelaney Merci, je suis entièrement d'accord! Comme j'ai essayé de l'expliquer dans ma réponse, il n'est pas vraiment utile de convertir quelque chose en une chose différente sans à un moment donné définir le type que vous utilisez réellement. L'intérêt des types est la vérification du type de temps du compilateur. Si vous avez juste besoin d'appeler l'objet, vous pouvez utiliser objectou dynamic. Si vous souhaitez charger dynamiquement des modules externes, vous pouvez demander aux classes de partager une interface commune et de convertir l'objet en cela. Si vous ne contrôlez pas le code tiers, créez de petits wrappers et implémentez l'interface à ce sujet.
Zyphrax le

Réponses:

203

Voici un exemple de conversion et de conversion:

using System;

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

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

Éditer:

Certaines personnes dans les commentaires disent que cette réponse ne répond pas à la question. Mais la ligne (T) Convert.ChangeType(input, typeof(T))offre la solution. La Convert.ChangeTypeméthode essaie de convertir n'importe quel objet au type fourni comme deuxième argument.

Par exemple:

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

J'ai écrit la réponse avec les génériques, parce que je pense qu'il est un signe très probable de l' odeur de code quand vous voulez lancer a somethingà a something elsesans traitement d' un type réel. Avec des interfaces appropriées qui ne devraient pas être nécessaires dans 99,9% des cas. Il y a peut-être quelques cas marginaux quand il s'agit de penser que cela pourrait avoir du sens, mais je recommanderais d'éviter ces cas.

Modifier 2:

Quelques conseils supplémentaires:

  • Essayez de garder votre code aussi sûr que possible. Si le compilateur ne connaît pas le type, il ne peut pas vérifier si votre code est correct et des choses comme la saisie semi-automatique ne fonctionneront pas. Dit simplement: si vous ne pouvez pas prédire le ou les types au moment de la compilation, comment le compilateur pourrait-il le faire ?
  • Si les classes avec lesquelles vous travaillez implémentent une interface commune , vous pouvez convertir la valeur en cette interface. Sinon, envisagez de créer votre propre interface et demandez aux classes d'implémenter cette interface.
  • Si vous travaillez avec des bibliothèques externes que vous importez dynamiquement, recherchez également une interface commune. Sinon, envisagez de créer de petites classes wrapper qui implémentent l'interface.
  • Si vous souhaitez effectuer des appels sur l'objet, mais ne vous souciez pas du type, stockez la valeur dans une variable objectou dynamic.
  • Les génériques peuvent être un excellent moyen de créer du code réutilisable qui s'applique à de nombreux types différents, sans avoir à connaître les types exacts impliqués.
  • Si vous êtes bloqué, envisagez une approche différente ou un remaniement de code. Votre code doit-il vraiment être aussi dynamique? Doit-il tenir compte de n'importe quel type?
Zyphrax
la source
145
Je ne sais pas comment cela aide OP. Elle a une variable de type, pas en Ttant que telle.
nawfal
12
@nawfal, la ligne Convert.ChangeType(input, typeof(T));donne essentiellement la solution. Vous pouvez facilement le remplacer typeof(T)par une variable de type existante. Une meilleure solution (si possible) serait d'empêcher le type dynamique tous ensemble.
Zyphrax
59
@Zyphrax, non, il nécessite toujours un casting Tauquel il n'est pas disponible.
nawfal
4
Je sais que l'objet résultant est vraiment de type Tmais vous n'obtenez toujours qu'un objectcomme référence. hmm, j'ai trouvé la question intéressante dans la prémisse que OP n'a que la Typevariable et pas d'autres informations. Comme si la signature de la méthode était Convert(object source, Type destination):) Néanmoins, je reçois votre point
nawfal
10
Comment est-ce une solution à cette question? J'ai le même problème et je n'ai pas de <T> générique. Je n'ai qu'une variable de type.
Nuri Tasdemir
114

D'autres réponses ne mentionnent pas le type "dynamique". Donc, pour ajouter une réponse de plus, vous pouvez utiliser le type "dynamique" pour stocker votre objet résultant sans avoir à transtyper l'objet converti avec un type statique.

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

Gardez à l'esprit qu'avec l'utilisation de "dynamique" le compilateur contourne la vérification de type statique qui pourrait introduire des erreurs d'exécution possibles si vous ne faites pas attention.

maulik13
la source
19
Ceci est la bonne réponse. Sans le mot-clé dynamique typeof (changedObj) est "objet". Avec le mot clé dynamique, il fonctionne parfaitement et typeof (changedObject) reflète correctement le même type que typeVar. De plus, vous n'avez pas besoin de (T) lancer ce que vous ne pouvez pas faire si vous ne connaissez pas le type.
rushinge
5
J'ai l'exception "Object must implement IConvertible" lors de l'utilisation de cette solution. De l'aide?
Nuri Tasdemir
@NuriTasdemir Difficile à dire, mais je crois que la conversion que vous faites n'est pas possible sans IConvertible. Quels sont les types impliqués dans votre conversion?
maulik13
Bien que cela fonctionne, il y a une pénalité de performance avec l'utilisation de la dynamique. Je recommanderais de ne pas les utiliser à moins que vous ne travailliez avec d'autres runtimes (ce pour quoi la dynamique a été conçue).
Bolo
19

Voici ma méthode pour convertir un objet mais pas en une variable de type générique, plutôt en une System.Typedynamique:

Je crée une expression lambda au moment de l'exécution en utilisant System.Linq.Expressions, de type Func<object, object>, qui déballe son entrée, effectue la conversion de type souhaitée puis donne le résultat encadré. Un nouveau est nécessaire non seulement pour tous les types qui sont castés, mais aussi pour les types qui sont castés (en raison de l'étape de déballage). La création de ces expressions prend beaucoup de temps, en raison de la réflexion, de la compilation et de la construction de méthode dynamique qui se fait sous le capot. Heureusement, une fois créées, les expressions peuvent être invoquées à plusieurs reprises et sans surcharge, donc je mets chacune en cache.

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

Notez que ce n'est pas magique. La conversion ne se produit pas dans le code, comme c'est le cas avec le dynamicmot clé, seules les données sous-jacentes de l'objet sont converties. Au moment de la compilation, il nous reste à déterminer minutieusement exactement quel type notre objet pourrait être, ce qui rend cette solution impraticable. J'ai écrit cela comme un hack pour invoquer des opérateurs de conversion définis par des types arbitraires, mais peut-être que quelqu'un peut trouver un meilleur cas d'utilisation.

balage
la source
2
Requiertusing System.Linq.Expressions;
Aaron D
4
Pour moi, cela souffre du même problème que la réponse de Zyphrax. Je ne peux pas invoquer de méthodes sur l'objet retourné car il est toujours de type "objet". Que j'utilise sa méthode ("a" ci-dessous) ou votre méthode ("b" ci-dessous) j'obtiens la même erreur sur le (t) cast - "'t' est une variable mais elle est utilisée comme un type.Type t = typeof(MyGeneric<>).MakeGenericType(obj.OutputType); var a = (t)Convert.ChangeType(obj, t); var b = (t)Caster.Cast(t, obj);
muusbolla
@muusbolla La réponse originale de Zyphrax utilise des génériques et des variables de type, non Type. Vous ne pouvez pas transtyper en utilisant la syntaxe de transtypage normale si vous ne disposez que de l'objet Type. Si vous souhaitez pouvoir utiliser l'objet en tant que type T au moment de la compilation, et non à l'exécution, vous devez le convertir à l'aide d'une variable de type ou simplement du nom de type réel. Vous pouvez faire le premier en utilisant la réponse de Zaphrax.
Ashley
8

Mis à part la boxe et le déballage pour plus de simplicité, aucune action d'exécution spécifique n'est impliquée dans la coulée le long de la hiérarchie d'héritage. C'est surtout une question de temps de compilation. Essentiellement, un transtypage indique au compilateur de traiter la valeur de la variable comme un autre type.

Que pourriez-vous faire après le casting? Vous ne connaissez pas le type, vous ne pourrez donc pas appeler de méthode dessus. Il n'y aurait rien de spécial à faire. Plus précisément, il ne peut être utile que si vous connaissez les types possibles au moment de la compilation, convertissez-les manuellement et gérez chaque cas séparément avec des ifinstructions:

if (type == typeof(int)) {
    int x = (int)obj;
    DoSomethingWithInt(x);
} else if (type == typeof(string)) {
    string s = (string)obj;
    DoSomethingWithString(s);
} // ...
Mehrdad Afshari
la source
1
Pourriez-vous expliquer cela plus clairement par rapport à ma question?
theringostarrs
Ce que j'essaie d'expliquer, c'est ce que tu pourrais faire après ça? Vous ne pouvez pas faire grand-chose car le compilateur C # nécessite un typage statique pour pouvoir faire quelque chose d'utile avec l'objet
Mehrdad Afshari
Vous avez raison. Je connais les types attendus de deux variables qui sont envoyées à la méthode en tant que type 'objet'. Je souhaite convertir les types attendus stockés dans des variables et les ajouter à la collection. Il est beaucoup plus facile de créer une branche sur le type et de tenter une conversion normale et des erreurs de capture.
theringostarrs
4
Votre réponse est bonne, mais juste pour être pointilleux, je note que les transformations n'affectent jamais les variables . Il n'est jamais légal de convertir une variable en une variable d'un autre type; les types de variables sont invariants en C #. Vous pouvez uniquement convertir la valeur stockée dans la variable en un autre type.
Eric Lippert
L'introduction de la frappe dynamique par C # 4.0 modifie-t-elle cette réponse?
Daniel T.
6

Comment pouvez vous faire ça? Vous avez besoin d'une variable ou d'un champ de type T où vous pouvez stocker l'objet après la conversion, mais comment pouvez-vous avoir une telle variable ou un champ si vous ne connaissez T qu'au moment de l'exécution? Donc, non, ce n'est pas possible.

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?
Daniel Brückner
la source
3
Si vous utilisez une classe générique, qui définit une méthode avec une valeur de retour de type T, vous pourriez avoir besoin de le faire. Par exemple, analyser une chaîne en une instance de T et la renvoyer.
Oliver Friedrich
7
Ce n'est heureusement pas la bonne réponse. Voir la réponse de maulik13.
rushinge
3
Où au nom du ciel trouvez-vous une CastTométhode Object?
ProfK
3

En ce qui concerne le casting au type 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;
    }

Et vous l'appellerez comme ça:

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

Cela était essentiel pour moi en cas d'obtention de la valeur d'attribut Description de plusieurs types d'énumération par valeur 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();
    }

puis:

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

Alternativement (meilleure approche), un tel casting pourrait ressembler à ceci:

 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));
    }
krzyski
la source
1

Après n'avoir rien trouvé pour contourner l'exception "Object must implement IConvertible" lors de l'utilisation de la réponse de Zyphrax (sauf pour implémenter l'interface). J'ai essayé quelque chose d'un peu peu conventionnel et j'ai travaillé pour ma situation.

Utilisation du package de nuget Newtonsoft.Json ...

var castedObject = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(myObject), myType);
Sec
la source
1

Harm, le problème est que vous n'avez pas de T.

vous n'avez qu'une variable Type.

Astuce pour MS, si vous pouviez faire quelque chose comme

TryCast<typeof(MyClass)>

si résoudrait tous nos problèmes.

user2825546
la source
0

Je ne comprendrai jamais pourquoi vous avez besoin de jusqu'à 50 points de réputation pour laisser un commentaire, mais je viens de dire que la réponse @Curt est exactement ce que je cherchais et j'espère que quelqu'un d'autre.

Dans mon exemple, j'ai un ActionFilterAttribute que j'utilisais pour mettre à jour les valeurs d'un correctif json. Je ne savais pas ce qu'était le modèle T pour le document de correction.J'ai dû le sérialiser et le désérialiser en un simple JsonPatchDocument, le modifier, puis parce que j'avais le type, le sérialiser et le désérialiser à nouveau vers le type.

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 );
Leye Eltee Taiwo
la source
-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;
}
user2008563
la source
2
Pourriez-vous indiquer en quoi cette réponse diffère des autres réponses et où cette solution est appropriée?
Klaus Gütter
-2

encore plus propre:

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

        t = (T)o;
        return true;
    }
Salomons Harm
la source
-2

Si vous devez convertir des objets à l'exécution sans connaître le type de destination, vous pouvez utiliser la réflexion pour créer un convertisseur dynamique.

Il s'agit d'une version simplifiée (sans mise en cache de la méthode générée):

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

alors vous pouvez l'appeler:

    var r = Tool.DynamicCast(myinstance, typeof (MyClass));
marianop
la source