60 votes

Puis-je sérialiser un objet Type C# ?

J'essaie de sérialiser un objet Type de la manière suivante :

Type myType = typeof (StringBuilder);
var serializer = new XmlSerializer(typeof(Type));
TextWriter writer = new StringWriter();
serializer.Serialize(writer, myType);

Lorsque je fais cela, l'appel à Serialize génère l'exception suivante :

"Le type System.Text.StringBuilder n'était pas attendu. Utilisez l'attribut XmlInclude ou SoapInclude pour spécifier des types qui ne sont pas connus statiquement".

Existe-t-il un moyen pour moi de sérialiser l' Type objet ? Notez que je n'essaie pas de sérialiser l'objet StringBuilder lui-même, mais le Type contenant les métadonnées de l'objet StringBuilder classe.

2 votes

Pourquoi sérialiser le Type ? Si la désérialisation n'est pas .Net, elle ne peut pas l'utiliser. Si elle l'est, tout ce que vous devez transmettre est le nom pleinement qualifié.

0 votes

Ce code exact soulève une exception dans .net 6.1 : Une erreur s'est produite lors de la génération du document XML. System.RuntimeType est inaccessible à cause de son niveau de protection. Seuls les types publics peuvent être traités.

98voto

Brian Sullivan Points 6392

Je ne savais pas qu'un objet Type pouvait être créé avec seulement une chaîne contenant le nom entièrement qualifié. Pour obtenir le nom entièrement qualifié, vous pouvez utiliser ce qui suit :

string typeName = typeof (StringBuilder).FullName;

Vous pouvez ensuite faire persister cette chaîne comme vous le souhaitez, puis reconstruire le type comme ceci :

Type t = Type.GetType(typeName);

Si vous devez créer une instance du type, vous pouvez le faire :

object o = Activator.CreateInstance(t);

Si vous vérifiez la valeur de o.GetType(), elle sera StringBuilder, comme vous vous y attendiez.

9 votes

Attention, Type.GetType(typeName) ; ne fonctionnera que pour les types de la même assemblée que l'appel.

53 votes

La solution est d'utiliser AssemblyQualifiedName au lieu de simplement FullName

9 votes

Type.GetType() échouera pour les types génériques.

14voto

hypehuman Points 225

J'ai eu le même problème, et ma solution a été de créer une classe SerializableType. Elle convertit librement vers et depuis System.Type, mais elle sérialise comme une chaîne de caractères. Tout ce que vous avez à faire est de déclarer la variable comme un SerializableType, et à partir de là vous pouvez vous y référer comme System.Type.

Voici la classe :

// a version of System.Type that can be serialized
[DataContract]
public class SerializableType
{
    public Type type;

    // when serializing, store as a string
    [DataMember]
    string TypeString
    {
        get
        {
            if (type == null)
                return null;
            return type.FullName;
        }
        set
        {
            if (value == null)
                type = null;
            else
            {
                type = Type.GetType(value);
            }
        }
    }

    // constructors
    public SerializableType()
    {
        type = null;
    }
    public SerializableType(Type t)
    {
        type = t;
    }

    // allow SerializableType to implicitly be converted to and from System.Type
    static public implicit operator Type(SerializableType stype)
    {
        return stype.type;
    }
    static public implicit operator SerializableType(Type t)
    {
        return new SerializableType(t);
    }

    // overload the == and != operators
    public static bool operator ==(SerializableType a, SerializableType b)
    {
        // If both are null, or both are same instance, return true.
        if (System.Object.ReferenceEquals(a, b))
        {
            return true;
        }

        // If one is null, but not both, return false.
        if (((object)a == null) || ((object)b == null))
        {
            return false;
        }

        // Return true if the fields match:
        return a.type == b.type;
    }
    public static bool operator !=(SerializableType a, SerializableType b)
    {
        return !(a == b);
    }
    // we don't need to overload operators between SerializableType and System.Type because we already enabled them to implicitly convert

    public override int GetHashCode()
    {
        return type.GetHashCode();
    }

    // overload the .Equals method
    public override bool Equals(System.Object obj)
    {
        // If parameter is null return false.
        if (obj == null)
        {
            return false;
        }

        // If parameter cannot be cast to SerializableType return false.
        SerializableType p = obj as SerializableType;
        if ((System.Object)p == null)
        {
            return false;
        }

        // Return true if the fields match:
        return (type == p.type);
    }
    public bool Equals(SerializableType p)
    {
        // If parameter is null return false:
        if ((object)p == null)
        {
            return false;
        }

        // Return true if the fields match:
        return (type == p.type);
    }
}

et un exemple d'utilisation :

[DataContract]
public class A
{

    ...

    [DataMember]
    private Dictionary<SerializableType, B> _bees;

    ...

    public B GetB(Type type)
    {
        return _bees[type];
    }

    ...

}

Vous pourriez également envisager d'utiliser AssemblyQualifiedName au lieu de Type.FullName - voir le commentaire de @GreyCloud

3 votes

+1 Aussi, il peut être utile de remplacer toString() ainsi que pour retourner return this.Type?.ToString(); de sorte que cette classe peut être utilisée de manière transparente partout où vous utiliseriez une classe Type ordinaire.

10voto

Dzyann Points 664

Brian's La réponse fonctionne bien si le type est dans la même assembly que l'appel (comme GreyCloud l'a souligné dans l'un des commentaires). Donc, si le type se trouve dans une autre assembly, vous devez utiliser la fonction AssemblyQualifiedName comme GreyCloud l'a également souligné.

Cependant, comme le AssemblyQualifiedName enregistre la version, si vos assemblages ont une version différente de celle de la chaîne où vous avez le type, cela ne fonctionnera pas.

Dans mon cas, c'était un problème et je l'ai résolu comme suit :

string typeName = typeof (MyClass).FullName;

Type type = GetTypeFrom(typeName);

object myInstance = Activator.CreateInstance(type);

Méthode GetTypeFrom

private Type GetTypeFrom(string valueType)
    {
        var type = Type.GetType(valueType);
        if (type != null)
            return type;

        try
        {
            var assemblies = AppDomain.CurrentDomain.GetAssemblies();                

            //To speed things up, we check first in the already loaded assemblies.
            foreach (var assembly in assemblies)
            {
                type = assembly.GetType(valueType);
                if (type != null)
                    break;
            }
            if (type != null)
                return type;

            var loadedAssemblies = assemblies.ToList();

            foreach (var loadedAssembly in assemblies)
            {
                foreach (AssemblyName referencedAssemblyName in loadedAssembly.GetReferencedAssemblies())
                {
                    var found = loadedAssemblies.All(x => x.GetName() != referencedAssemblyName);

                    if (!found)
                    {
                        try
                        {
                            var referencedAssembly = Assembly.Load(referencedAssemblyName);
                            type = referencedAssembly.GetType(valueType);
                            if (type != null)
                                break;
                            loadedAssemblies.Add(referencedAssembly);
                        }
                        catch
                        {
                            //We will ignore this, because the Type might still be in one of the other Assemblies.
                        }
                    }
                }
            }                
        }
        catch(Exception exception)
        {
            //throw my custom exception    
        }

        if (type == null)
        {
            //throw my custom exception.
        }

        return type;
    }

Je poste ceci au cas où quelqu'un en aurait besoin.

2voto

rjzii Points 8979

Selon la documentation MSDN de System.Type [1], vous devriez être en mesure de sérialiser l'objet System.Type. Cependant, comme l'erreur fait explicitement référence à System.Text.StringBuilder, c'est probablement la classe qui cause l'erreur de sérialisation.

[1] Classe de type (système) - http://msdn.microsoft.com/en-us/library/system.type.aspx

1voto

AdamSane Points 1825

Je viens de regarder sa définition, elle n'est pas marquée comme Serializable. Si vous avez vraiment besoin que ces données soient sérialisées, vous devrez peut-être les convertir en une classe personnalisée marquée comme telle.

public abstract class Type : System.Reflection.MemberInfo
    Member of System

Summary:
Represents type declarations: class types, interface types, array types, value types, enumeration types, type parameters, generic type definitions, and open or closed constructed generic types.

Attributes:
[System.Runtime.InteropServices.ClassInterfaceAttribute(0),
System.Runtime.InteropServices.ComDefaultInterfaceAttribute(System.Runtime.InteropServices._Type),
System.Runtime.InteropServices.ComVisibleAttribute(true)]

2 votes

C'est faux, System.Type n'est pas sérialisable mais l'implémentation concrète System.RuntimeType est.

0 votes

Maintenant, il est décoré avec l'attribut Serializable.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X