views:

150

answers:

3

I have an existing class for serializing and deserializing objects to/from XML. It's a generic class with a single type parameter T whose only constraint is where T : IXmlSerializable. However, I want to still be able to use this class on classes that do not implement IXmlSerializable but have the [Serializable] attribute. How could I go about doing this?

From my generic class:

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

I found this discussion but there was no solution given, just that I can't do where T : Serializable. Trying to do where T : SerializableAttribute makes Visual Studio say "Cannot use sealed class 'System.SerializableAttribute' as type parameter constraint".

Edit: based on Stephen's answer, I removed the constraints on XmlSerializationUtils<T> and added this static constructor:

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"
        );
    }
}
+3  A: 

You can't require an attribute as part of generics. However, you could provide a static constructor that checks for it and throws if it's not found.

Steven Sudit
+1  A: 

I'd just eliminate the type constraint and catch the SerializationException when the type does not serialize or deserialize properly... In fact, this allows your generic Serialize and Deserialize methods to accept a formatter

public enum Formatter { Binary, Xml }

that could control whether the serialization is binary or 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
}
Charles Bretana
That's not an unreasonable solution.
Steven Sudit
Yeah i agree, it is being used by a developer who whould know whether the class he is trying to serialise is serliasable, if he uses it wrong that is what exceptions are for, you cannot eliminate every posible bug at compile time.
Ben Robinson
@Ben: We can't always do so, but we should certainly try to catch bugs early and often. In this case, we can't catch it at compile time, but if we use the static constructor trick, we can catch it at the very start of run time (which means a post-compile smoke check will not miss it).
Steven Sudit
When I origianlly coded this, I had code in the class to detect, (only when attempting Xml Serialization), if the passed in Type variable implemented `IXmlSerializable`, or (using Reflection), was decorated with the `[Serializable]` attribute... Then WCF came out and they added the `[DataContract]` attribute, which also allows a type to be serialized as Xml. So I removed it...
Charles Bretana
+2  A: 

You can check to see if a type is serializable using the IsSerializable property of the Type of the object.

myObj.GetType().IsSerializable

As mentioned, this isn't possible to add as a generic constraint, but would most likely be checked in a constructor.

womp
This is better than checking for the attribute. Thanks.
Steven Sudit
Hm. My current solution (in my question) checks to see if `IXmlSerializable` or `[Serializable]` applies to the given class `T`. Does `IsSerializable` account for both?
Sarah Vessels
Apparently. See http://msdn.microsoft.com/en-us/library/system.type.isserializable.aspx
Steven Sudit