2010-07-20 8 views
6

J'ai une classe existante pour la sérialisation et la désérialisation d'objets vers/depuis XML. C'est une classe générique avec un seul paramètre de type T dont la seule contrainte est where T : IXmlSerializable. Cependant, je veux toujours pouvoir utiliser cette classe sur les classes qui n'implémentent pas IXmlSerializable mais qui ont l'attribut [Serializable]. Comment pourrais-je faire cela?Classe d'utilitaire de sérialisation générique C#

de ma classe générique:

public static class XmlSerializationUtils<T> where T : IXmlSerializable 
{ 
    public static T DeserializeXml(XmlDocument xml) { ... } 
    public static XmlDocument SerializeToXml(T toSerialize) { ... } 
} 

J'ai trouvé this discussion mais il n'y avait pas de solution donnée, juste que je ne peux pas faire where T : Serializable. Essayer de faire where T : SerializableAttribute fait Visual Studio dire "Impossible d'utiliser la classe scellée 'System.SerializableAttribute' comme contrainte de paramètre de type".

Edit: basé sur Stephen's answer, je supprimé les restrictions sur XmlSerializationUtils<T> et a ajouté ce constructeur statique:

static XmlSerializationUtils() 
{ 
    Type type = typeof(T); 
    bool hasAttribute = null != Attribute.GetCustomAttribute(type, 
     typeof(SerializableAttribute)); 
    bool implementsInterface = 
     null != type.GetInterface(typeof(IXmlSerializable).FullName); 
    if (!hasAttribute && !implementsInterface) 
    { 
     throw new ArgumentException(
      "Cannot use XmlSerializationUtils on class " + type.Name + 
      " because it does not have the Serializable attribute " + 
      " and it does not implement IXmlSerializable" 
     ); 
    } 
} 

Répondre

6

Vous ne pouvez pas exiger un attribut dans le cadre des génériques. Cependant, vous pouvez fournir un constructeur statique qui le vérifie et le lance s'il n'est pas trouvé.

5

Je venais juste d'éliminer les contraintes de type et attraper le SerializationException lorsque le type ne sérialise pas ou désérialiser correctement ... En fait, cela permet à votre sérialisation générique et deserialize méthodes pour accepter un formatter

public enum Formatter { Binary, Xml } 

qui pourrait contrôler si la sérialisation est binaire ou Xml

public class Serialization 
{ 
    public enum Formatter { Binary, Xml } 

    #region Serialization methods 
    public static void Serialize2File<T>(T obj, string pathSpec, 
     Formatter formatter) 
    { 
     try 
     { 
      switch (formatter) 
      { 
       case (Formatter.Binary): 
        using (var fs = new FileStream(pathSpec, FileMode.Create, 
             FileAccess.Write, FileShare.Write)) 
         (new BinaryFormatter()).Serialize(fs, obj); 
        break; 

       case (Formatter.Xml): 
        var serializer = new XmlSerializer(typeof(T)); 
        TextWriter textWriter = new StreamWriter(pathSpec); 
        serializer.Serialize(textWriter, obj); 
        textWriter.Close(); 
        break; 

       default: 
        throw new MyCustomException("Invalid Formatter option"); 
      } 
     } 
     catch (SerializationException sX) 
     { 
      var errMsg = String.Format(
       "Unable to serialize {0} into file {1}", 
       obj, pathSpec); 
      throw new MyCustomException(errMsg, sX); 
     } 
    } 
    public static T DeSerializeFromFile<T>(string pathSpec, 
     Formatter formatter) where T : class 
    { 
     try 
     { 
      switch (formatter) 
      { 
       case (Formatter.Binary): 
        using (var strm = new FileStream(pathSpec, 
             FileMode.Open, FileAccess.Read)) 
        { 
         IFormatter fmt = new BinaryFormatter(); 
         var o = fmt.Deserialize(strm); 
         if (!(o is T)) 
          throw new ArgumentException("Bad Data File"); 
         return o as T; 
        } 

       case (Formatter.Xml): 
        var serializer = new XmlSerializer(typeof(T)); 
        TextReader rdr = new StreamReader(pathSpec); 
        return (T)serializer.Deserialize(rdr); 

       default: 
        throw new MyCustomException("Invalid Formatter option"); 
      } 
     } 
     catch (SerializationException sX) 
     { 
      var errMsg = String.Format(
       "Unable to deserialize {0} from file {1}", 
       typeof(T), pathSpec); 
      throw new MyCustomException(errMsg, sX); 
     } 
    } 
    #endregion Serialization methods 
} 
+0

Ce n'est pas une solution déraisonnable. –

+0

Ouais je suis d'accord, il est utilisé par un développeur qui sait si la classe qu'il essaie de sérialiser est sériable, s'il ne l'utilise pas correctement, vous ne pouvez pas éliminer tous les bogues posibles au moment de la compilation. –

+0

@Ben: Nous ne pouvons pas toujours le faire, mais nous devrions certainement essayer d'attraper les bugs tôt et souvent. Dans ce cas, nous ne pouvons pas l'attraper au moment de la compilation, mais si nous utilisons l'astuce du constructeur statique, nous pouvons l'attraper au tout début de l'exécution (ce qui signifie qu'un contrôle de fumée post-compilation ne le manquera pas). –

8

Vous pouvez vérifier si un type est sérialisable en utilisant la propriété IsSerializable du type de l'objet. Comme mentionné, ceci n'est pas possible à ajouter en tant que contrainte générique, mais serait probablement vérifié dans un constructeur.

+0

C'est mieux que de vérifier l'attribut. Merci. –

+0

Hm. Ma solution actuelle (dans ma question) vérifie si 'IXmlSerializable' ou' [Serializable] 's'applique à la classe donnée' T'. Est-ce que 'IsSerializable' représente les deux? –

+0

Apparemment. Voir http://msdn.microsoft.com/fr-fr/library/system.type.isserializable.aspx –