'C# generic serialization utility class

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


Solution 1:[1]

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.

Solution 2:[2]

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
}

Solution 3:[3]

C# generic serialization utility class.

The core of serializing classes that implement the IXmlSerializable or Serializable attribute is to check the given type before passing it to the serializer.

XmlDocument Serialize<T>(T obj)
{
    Type type = typeof(T);
    if (type.HasAttribute<SerializableAttribute>()
      | type.ImplementsInterface<IXmlSerializableAttribute>())
        return XmlSerializer<T>.Serialize(obj);
    throw new InvalidOperationException("Unserializable object given.");
}        

To do this, you should implement the following extension methods and a generic serializer.

using System.ComponentModel;
using System.Linq;
using System.Reflection;
using System.IO;
using System.Text;
using System.Xml.Schema;

namespace System
{
    public static partial class Extensions
    {
        /// <summary>
        /// Checks if the object <see cref="Type"/> has the specified attribute.
        /// </summary>
        /// <param name="type">
        /// Object <see cref="Type"/> for which you want to check whether
        /// whether it has <see cref="Attribute"/> specified by the <typeparamref name="T"/>.
        /// </param>
        public static bool HasAtribute<T>(this Type type) where T : Attribute
        {
            return type.GetCustomAttributes(typeof(T), true).Any();
        }
    
        /// <summary>
        /// Checks if the object <see cref="Type"/> implements the specified interface.
        /// </summary>
        /// <param name="type">
        /// Object <see cref="Type"/> for which you want to check whether
        /// whether it implements the interface specified by the <paramref name="interfaceType"/> parameter.
        /// </param>
        /// <param name="interfaceType">
        /// The <see cref="Type"/> being tested, which is an interface.
        /// </param>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="type"/> is not a class, value type, or interface,
        /// and also if <paramref name="interfaceType"/> is not an interface.
        /// </exception>
        /// <exception cref="ArgumentException">
        /// The value <see langword="null"/> was passed as one of the parameters.
        /// </exception>
        /// <returns>
        /// <see langword="true"/> if the object <see cref="Type"/> implements the specified interface.
        /// </returns> 
        public static bool ImplementsInterface(this Type type, Type interfaceType)
        {
            if (type == null)
                throw new ArgumentNullException(nameof(type));
            if (interfaceType == null)
                throw new ArgumentNullException(nameof(interfaceType));
            if (!interfaceType.IsInterface)
                throw new ArgumentException("Argument must be interface.", 
                    nameof(interfaceType));

            while (type != null)
            {
                Type[] interfaces = type.GetInterfaces();
                {
                    int length = interfaces.Length;
                    for (int i = 0; i < length; i++)
                    {
                        if (interfaces[i] == interfaceType || interfaces[i].ImplementsInterface(interfaceType))
                            return true;
                    }
                }
                type = type.BaseType;
            }
            return false;
        }

        /// <summary>
        /// Checks if the object <see cref="Type"/> implements the specified interface.
        /// </summary>
        /// <typeparam name="T">
        /// The type being checked, which is an interface.
        /// </typeparam>
        /// <param name="type">
        /// Object <see cref="Type"/> for which you want to check whether
        /// whether it implements the specified interface <typeparamref name="T"/>.
        /// </param>
        /// <returns>
        /// <see langword="true"/> if the object is <see cref="Type"/>
        /// implements the <typeparamref name="T"/> interface.
        /// </returns>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="type"/> is not a class, value type, or interface.
        /// </exception>
        /// <exception cref="ArgumentException">
        /// The value <see langword="null"/> was passed as <paramref name="type"/>.
        /// </exception>
        public static bool ImplementsInterface<T>(this Type type) where T : class =>
            ImplementsInterface(type, typeof(T)); 

    }
}

namespace System.Xml.Serialization
{
    /// <summary>
    /// Serializes and deserializes <typeparamref name="T"/> objects into XML documents.
    /// Allows you to control the process of encoding objects in XML.
    /// </summary>
    /// <typeparam name="T">Object type.</typeparam>
    public static class XmlSerializer<T>
    {
        private static readonly XmlSerializer _serializer = new XmlSerializer(typeof(T));
        private static readonly XmlWriterSettings _defaultWriterSettings = new XmlWriterSettings
        {
            CheckCharacters = false,
            CloseOutput = false,
            ConformanceLevel = ConformanceLevel.Auto,
            Encoding = DefaultEncoding,
            Indent = true,
            IndentChars = "\t",
            NamespaceHandling = NamespaceHandling.OmitDuplicates,
            NewLineChars = "\r\n",
            NewLineHandling = NewLineHandling.Replace,
            NewLineOnAttributes = false,
            OmitXmlDeclaration = false
        };
        private static readonly XmlReaderSettings _defaultReaderSettings = new XmlReaderSettings
        {
            CheckCharacters = false,
            CloseInput = false,
            ConformanceLevel = ConformanceLevel.Auto,
            DtdProcessing = DtdProcessing.Prohibit,
            IgnoreComments = true,
            IgnoreProcessingInstructions = true,
            IgnoreWhitespace = true,
            LineNumberOffset = 0,
            LinePositionOffset = 0,
            MaxCharactersFromEntities = 0,
            MaxCharactersInDocument = 0,
            NameTable = null,
            ValidationFlags = XmlSchemaValidationFlags.None,
            ValidationType = ValidationType.None,
            XmlResolver = null
        };

        /// <summary>
        /// Default character encoding.
        /// </summary>
        public static Encoding DefaultEncoding => Encoding.UTF8;

        /// <summary>
        /// Default settings for the <see cref="XmlWriter" /> instance being created.
        /// </summary>
        public static XmlWriterSettings DefaultXmlWriterSettings => _defaultWriterSettings.Clone();

        /// <summary>
        /// Default settings for the <see cref="XmlReader" /> instance that is created.
        /// </summary>
        public static XmlReaderSettings DefaultXmlReaderSettings => _defaultReaderSettings.Clone();

        /// <summary>
        /// Serializes the given object and returns an XML document.
        /// </summary>
        /// <param name="o">
        /// An instance <typeparamref name="T"/> to serialize.
        /// </param>
        /// <param name="settings">
        /// Settings for the new <see cref="XmlWriter" /> instance.
        /// If <see langword="null"/> is specified,
        /// settings are used <see cref="DefaultXmlWriterSettings"/>.
        /// </param>
        /// <returns>An instance of <see cref="XmlDocument"/> that represents given object.</returns>
        public static XmlDocument Serialize(T o, XmlWriterSettings settings = null)
        {
            StringBuilder sb = new StringBuilder();
            using (XmlWriter xmlWriter = XmlWriter.Create(sb, settings ?? DefaultXmlWriterSettings))
                _serializer.Serialize(xmlWriter, o, (XmlSerializerNamespaces)null);
            XmlDocument xmlDocument = new XmlDocument();
            xmlDocument.LoadXml(sb.ToString());
            return xmlDocument;
        }

        /// <summary>
        /// Deserializes the object contained in the specified XML document.
        /// </summary>
        /// <param name="xmlDocument">
        /// An XML document containing the serialized data.
        /// </param>
        /// <param name="settings">
        /// Settings for the new <see cref="XmlReader" /> instance.
        /// If <see langword="null"/> is specified,
        /// settings are used <see cref="DefaultXmlReaderSettings"/>.
        /// </param>
        /// <returns> The deserialized object of type <typeparamref name="T"/>. </returns>
        public static T Deserialize(XmlDocument xmlDocument, XmlReaderSettings settings)
        {
            string text = xmlDocument.OuterXml;
            using (StringReader reader = new StringReader(text))
            using (XmlReader xmlReader = XmlReader.Create(reader, DefaultXmlReaderSettings))
                return (T)_serializer.Deserialize(xmlReader);

        }

        /// <summary>
        /// Returns a value indicating whether this <see cref="XmlSerializer" /> can deserialize the specified XML document.
        /// </summary>
        /// <param name="xmlReader">
        /// <see cref="XmlReader" /> Pointing to the document to deserialize.
        /// </param>
        /// <returns>
        /// <see langword="true" /> If this <see cref="XmlSerializer" /> can deserialize an object, <see cref="XmlReader" /> indicates; otherwise, <see langword="false" />.
        /// </returns>
        public static bool CanDeserialize(XmlReader xmlReader)
        {
            return _serializer.CanDeserialize(xmlReader);
        }
    }
}

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 womp
Solution 2 Charles Bretana
Solution 3