Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/.net/21.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# WCF:具有多个模块的数据协定序列化程序_C#_.net_Wcf_Serialization_Datacontractserializer - Fatal编程技术网

C# WCF:具有多个模块的数据协定序列化程序

C# WCF:具有多个模块的数据协定序列化程序,c#,.net,wcf,serialization,datacontractserializer,C#,.net,Wcf,Serialization,Datacontractserializer,在我的一个C#项目中,我使用一个WCF数据契约序列化程序来序列化为XML。然而,该框架由多个扩展模块组成,这些模块可能会被加载,也可能不会被加载,这取决于一些启动配置(我使用MEF以防出现问题)。在未来,模块列表可能会增加,我担心这种情况可能会在某一天对模块特定的数据造成问题。据我所知,我可以实现一个数据协定解析器来双向帮助序列化程序定位类型,但是如果项目包含它无法解释的数据,那么会发生什么情况,因为关联的模块没有加载 我正在寻找一种解决方案,允许我在没有加载完整模块集(甚至不可用)的情况下保留

在我的一个C#项目中,我使用一个WCF数据契约序列化程序来序列化为XML。然而,该框架由多个扩展模块组成,这些模块可能会被加载,也可能不会被加载,这取决于一些启动配置(我使用MEF以防出现问题)。在未来,模块列表可能会增加,我担心这种情况可能会在某一天对模块特定的数据造成问题。据我所知,我可以实现一个数据协定解析器来双向帮助序列化程序定位类型,但是如果项目包含它无法解释的数据,那么会发生什么情况,因为关联的模块没有加载

我正在寻找一种解决方案,允许我在没有加载完整模块集(甚至不可用)的情况下保留现有的序列化数据。我认为这是一种告诉反序列化程序“如果你不明白你得到了什么,那么不要尝试序列化它,但是请将数据保存在某个地方,以便下次序列化时可以将其放回”。我认为我的问题与序列化操作有关,但我(目前)没有很好地找到如何处理在序列化操作之间添加或删除复杂类型的提示

最简单的例子: 假设我使用可选模块A、B和C启动应用程序,并生成以下XML(AData、BData和CData位于一个集合中,并且可能都派生自一个公共基类):



.

您的问题是您有一个,并且您希望使用
DataContractSerializer
读取和保存“未知”已知类型,特别是带有类型提示的XML元素,该类型提示引用当前未加载到您的应用程序域中的类型

不幸的是,这个用例并不是由往返机制实现的。该机制旨在缓存对象内的未知数据成员,前提是数据协定对象本身可以成功反序列化并实现。不幸的是,在您的情况下,由于多态子类型无法识别,无法精确构造数据协定对象;而是引发以下异常:

发生System.Runtime.Serialization.Serialization异常
Message=“第4行位置6.元素中的错误 “”包含 “”数据协定。该 反序列化程序不知道映射到此契约的任何类型。 将与“CData”对应的类型添加到已知类型列表中-对于 例如,使用KnownTypeAttribute属性或将其添加到 传递给DataContractSerializer的已知类型列表。“

即使我尝试创建一个用
[CollectionDataContract]
标记的自定义泛型集合,该集合实现
IExtensibleDataObject
以缓存具有无法识别的契约的项,也会引发相同的异常

一个解决方案是利用您的问题比往返问题稍微难的事实。您(软件架构师)实际上知道所有可能的多态子类型。您的软件不会,因为它并不总是加载包含它们的程序集。因此,当不需要真实类型时,可以加载轻量级虚拟类型而不是真实类型。只要伪类型实现
IExtensibleDataObject
,并且具有相同的数据契约名称空间和名称以及真实类型,它们的数据契约将与多态集合中的“真实”数据契约互换

因此,如果您按如下方式定义类型,请添加
Dummies.CData
dummy占位符:

public static class Namespaces
{
    // The data contract namespace for your project.
    public const string ProjectNamespace = "http://www.Question45412824.com"; 
}

// common base class
[DataContract(Namespace = Namespaces.ProjectNamespace)]
public class ModuleData : IExtensibleDataObject
{
    public ExtensionDataObject ExtensionData { get; set; }
}

[DataContract(Namespace = Namespaces.ProjectNamespace)]
public class AData : ModuleData
{
    [DataMember]
    public string A { get; set; }
}

[DataContract(Namespace = Namespaces.ProjectNamespace)]
public class BData : ModuleData
{
    [DataMember]
    public string B { get; set; }
}

[DataContract(Namespace = Namespaces.ProjectNamespace)]
[KnownType(typeof(AData))]
[KnownType(typeof(BData))]
public class Project
{
    [DataMember]
    public List<ModuleData> Data { get; set; }
}

[DataContract(Namespace = Namespaces.ProjectNamespace)]
public class CData : ModuleData
{
    [DataMember]
    public string C { get; set; }
}

namespace Dummies
{
    [DataContract(Namespace = Namespaces.ProjectNamespace)]
    public class CData : ModuleData
    {
    }
}

另一个解决方案是将您的
列表
替换为自定义集合,该集合实现
IXmlSerializable
,并完全手动处理多态序列化,在未知元素列表中缓存未知多态子类型的XML。但是,我不建议这样做,因为即使是简单的
IXmlSerializable
实现也可能相当复杂,如图所示,例如,dbc建议使用假人来利用往返机制来完成这项工作,我通过根据需要动态生成虚拟类型,使解决方案更加通用

此解决方案的核心是以下在内部调用C#编译器的简单函数:

private Type CreateDummyType(string typeName, string typeNamespace)
{
    var className = $"DummyClass_{random_.Next()}";
    var code = $"[System.Runtime.Serialization.DataContract(Name=\"{typeName}\", Namespace=\"{typeNamespace}\")] public class {className} : ModuleData {{}}";

    using (var provider = new CSharpCodeProvider())
    {
        var parameters = new CompilerParameters();
        parameters.ReferencedAssemblies.Add("System.Runtime.Serialization.dll");
        parameters.ReferencedAssemblies.Add(GetType().Assembly.Location); // this assembly (for ModuleData)

        var results = provider.CompileAssemblyFromSource(parameters, code);
        return results.CompiledAssembly.GetType(className);
    }
}
我将其与DataContractResolver相结合,DataContractResolver负责处理任何未知类型,并根据需要生成假人,以便在后续(反)序列化期间保留其数据

为了完整起见,我将示例代码的最新迭代放在这里:

using System;
using System.IO;
using System.Collections.Generic;
using System.Runtime.Serialization;
using System.Diagnostics;
using System.Xml;
using System.Xml.Linq;
using Microsoft.CSharp;
using System.CodeDom.Compiler;

public static class Namespaces
{
    public const string BaseNamespace = "http://www.Question45412824.com";
    public const string ProjectNamespace = BaseNamespace + "/Project";
    public const string ExtensionNamespace = BaseNamespace + "/Extension";
}

// common base class
[DataContract(Namespace = Namespaces.ProjectNamespace)]
public class ModuleData : IExtensibleDataObject
{
    public ExtensionDataObject ExtensionData { get; set; }
}

[DataContract(Namespace = Namespaces.ProjectNamespace)]
public class AData : ModuleData
{
    [DataMember]
    public string A { get; set; }
}

[DataContract(Namespace = Namespaces.ProjectNamespace)]
public class BData : ModuleData
{
    [DataMember]
    public string B { get; set; }
}

[DataContract(Namespace = Namespaces.ProjectNamespace)]
[KnownType(typeof(AData))]
[KnownType(typeof(BData))]
public class Project
{
    [DataMember]
    public List<ModuleData> Data { get; set; }
}

[DataContract(Namespace = Namespaces.ProjectNamespace)]
internal class CSubData : ModuleData
{
    [DataMember]
    public string Name { get; set; }
}


[DataContract(Namespace = Namespaces.ExtensionNamespace)]
public class CData : ModuleData
{
    [DataMember]
    public ModuleData C { get; set; }
}

class Program
{
    static void Main(string[] args)
    {
        new TestClass().Test();
    }
}

class TestClass
{
    public virtual void Test()
    {
        // new project object
        var project1 = new Project()
        {
            Data = new List<ModuleData>()
                {
                     new AData() { A = "A" },
                     new BData() { B = "B" },
                     new CData() { C = new CSubData() { Name = "C" } }
                }
        };

        // serialization; make CData explicitly known to simulate presence of "module C"
        var extraTypes = new[] { typeof(CData), typeof(CSubData) };

        ConsoleAndDebug.WriteLine("\n== Serialization with all types known ==");
        var xml = project1.SerializeXml(extraTypes);
        ConsoleAndDebug.WriteLine(xml);

        ConsoleAndDebug.WriteLine("\n== Deserialization and subsequent serialization WITH generic resolver and unknown types ==");
        TestDeserialize(project1, xml, new GenericDataContractResolver());

        ConsoleAndDebug.WriteLine("\n== Deserialization and subsequent serialization WITHOUT generic resolver and unknown types ==");
        try
        {
            // Demonstrate that the XML cannot be deserialized without the generic resolver.
            TestDeserialize(project1, xml, new Type[0]);
            Assert.IsTrue(false);
        }
        catch (AssertionFailedException ex)
        {
            Console.WriteLine("Caught unexpected exception: ");
            Console.WriteLine(ex);
            throw;
        }
        catch (Exception ex)
        {
            ConsoleAndDebug.WriteLine(string.Format("Caught expected exception: {0}", ex.Message));
        }
    }

    public void TestDeserialize<TProject>(TProject project, string xml, Type[] extraTypes)
    {
        TestDeserialize<TProject>(xml, extraTypes);
    }

    public void TestDeserialize<TProject>(string xml, Type[] extraTypes)
    {
        var project2 = xml.DeserializeXml<TProject>(extraTypes);

        var xml2 = project2.SerializeXml(extraTypes);

        ConsoleAndDebug.WriteLine(xml2);

        // Assert that the incoming and re-serialized XML are equivalent (no data was lost).
        Assert.IsTrue(XNode.DeepEquals(XElement.Parse(xml), XElement.Parse(xml2)));
    }

    public void TestDeserialize<TProject>(TProject project, string xml, DataContractResolver resolver)
    {
        TestDeserialize<TProject>(xml, resolver);
    }

    public void TestDeserialize<TProject>(string xml, DataContractResolver resolver)
    {
        var project2 = xml.DeserializeXml<TProject>(resolver);

        var xml2 = project2.SerializeXml(resolver);

        ConsoleAndDebug.WriteLine(xml2);

        // Assert that the incoming and re-serialized XML are equivalent (no data was lost).
        Assert.IsTrue(XNode.DeepEquals(XElement.Parse(xml), XElement.Parse(xml2)));
    }
}

public static partial class DataContractSerializerHelper
{
    public static string SerializeXml<T>(this T obj, Type[] extraTypes)
    {
        return obj.SerializeXml(new DataContractSerializer(obj == null ? typeof(T) : obj.GetType(), extraTypes));
    }

    public static string SerializeXml<T>(this T obj, DataContractResolver resolver)
    {
        return obj.SerializeXml(new DataContractSerializer(obj == null ? typeof(T) : obj.GetType(), null, int.MaxValue, false, false, null, resolver));
    }

    public static string SerializeXml<T>(this T obj, DataContractSerializer serializer)
    {
        serializer = serializer ?? new DataContractSerializer(obj == null ? typeof(T) : obj.GetType());
        using (var textWriter = new StringWriter())
        {
            var settings = new XmlWriterSettings { Indent = true };
            using (var xmlWriter = XmlWriter.Create(textWriter, settings))
            {
                serializer.WriteObject(xmlWriter, obj);
            }
            return textWriter.ToString();
        }
    }

    public static T DeserializeXml<T>(this string xml, DataContractResolver resolver)
    {
        return xml.DeserializeXml<T>(new DataContractSerializer(typeof(T), null, int.MaxValue, false, false, null, resolver));
    }

    public static T DeserializeXml<T>(this string xml, Type[] extraTypes)
    {
        return xml.DeserializeXml<T>(new DataContractSerializer(typeof(T), extraTypes));
    }

    public static T DeserializeXml<T>(this string xml, DataContractSerializer serializer)
    {
        using (var textReader = new StringReader(xml ?? ""))
        using (var xmlReader = XmlReader.Create(textReader))
        {
            return (T)(serializer ?? new DataContractSerializer(typeof(T))).ReadObject(xmlReader);
        }
    }
}

public static class ConsoleAndDebug
{
    public static void WriteLine(object s)
    {
        Console.WriteLine(s);
        Debug.WriteLine(s);
    }
}

public class AssertionFailedException : System.Exception
{
    public AssertionFailedException() : base() { }

    public AssertionFailedException(string s) : base(s) { }
}

public static class Assert
{
    public static void IsTrue(bool value)
    {
        if (value == false)
            throw new AssertionFailedException("failed");
    }
}

class GenericDataContractResolver : DataContractResolver
{
    private static readonly Random random_ = new Random();
    private static readonly Dictionary<Tuple<string, string>, Type> toType_ = new Dictionary<Tuple<string, string>, Type>();
    private static readonly Dictionary<Type, Tuple<string, string>> fromType_ = new Dictionary<Type, Tuple<string, string>>();

    private Type CreateDummyType(string typeName, string typeNamespace)
    {
        var className = $"DummyClass_{random_.Next()}";
        var code = $"[System.Runtime.Serialization.DataContract(Name=\"{typeName}\", Namespace=\"{typeNamespace}\")] public class {className} : ModuleData {{}}";

        using (var provider = new CSharpCodeProvider())
        {
            var parameters = new CompilerParameters();
            parameters.ReferencedAssemblies.Add("System.Runtime.Serialization.dll");
            parameters.ReferencedAssemblies.Add(GetType().Assembly.Location); // this assembly (for ModuleData)

            var results = provider.CompileAssemblyFromSource(parameters, code);
            return results.CompiledAssembly.GetType(className);
        }
    }

    // Used at deserialization; allows users to map xsi:type name to any Type 
    public override Type ResolveName(string typeName, string typeNamespace, Type declaredType, DataContractResolver knownTypeResolver)
    {
        var type = knownTypeResolver.ResolveName(typeName, typeNamespace, declaredType, null);

        // resolve all unknown extension datasets; all other should be explicitly known.
        if (type == null && declaredType == typeof(ModuleData) && typeNamespace == Namespaces.ExtensionNamespace)
        {
            // if we already have this type cached, then return the cached one
            var typeNameAndNamespace = new Tuple<string, string>(typeName, typeNamespace);
            if (toType_.TryGetValue(typeNameAndNamespace, out type))
                return type;

            // else compile the dummy type and remember it in the cache
            type = CreateDummyType(typeName, typeNamespace);
            toType_.Add(typeNameAndNamespace, type);
            fromType_.Add(type, typeNameAndNamespace);
        }

        return type;
    }

    // Used at serialization; maps any Type to a new xsi:type representation
    public override bool TryResolveType(Type type, Type declaredType, DataContractResolver knownTypeResolver, out XmlDictionaryString typeName, out XmlDictionaryString typeNamespace)
    {
        if (knownTypeResolver.TryResolveType(type, declaredType, null, out typeName, out typeNamespace))
            return true; // known type

        // is the type one of our cached dummies?
        var typeNameAndNamespace = default(Tuple<string, string>);
        if (declaredType == typeof(ModuleData) && fromType_.TryGetValue(type, out typeNameAndNamespace))
        {
            typeName = new XmlDictionaryString(XmlDictionary.Empty, typeNameAndNamespace.Item1, 0);
            typeNamespace = new XmlDictionaryString(XmlDictionary.Empty, typeNameAndNamespace.Item2, 0);
            return true; // dummy type
        }

        return false; // unknown type
    }
}
使用系统;
使用System.IO;
使用System.Collections.Generic;
使用System.Runtime.Serialization;
使用系统诊断;
使用System.Xml;
使用System.Xml.Linq;
使用Microsoft.CSharp;
使用System.CodeDom.Compiler;
公共静态类名称空间
{
public const string BaseNamespace=”http://www.Question45412824.com";
public const string ProjectNamespace=BaseNamespace+“/Project”;
public const string ExtensionNamespace=BaseNamespace+“/Extension”;
}
//公共基类
[DataContract(Namespace=Namespace.ProjectNamespace)]
公共类ModuleData:IEExtensibleDataObject
{
公共扩展数据对象扩展数据{get;set;}
}
[DataContract(Namespace=Namespace.ProjectNamespace)]
公共类数据:ModuleData
{
[数据成员]
公共字符串A{get;set;}
}
[DataContract(Namespace=Namespace.ProjectNamespace)]
公共类数据:ModuleData
{
[数据成员]
公共字符串B{get;set;}
}
[DataContract(Namespace=Namespace.ProjectNamespace)]
[KnownType(typeof(AData))]
[KnownType(typeof(BData))]
公共类项目
{
[数据成员]
公共列表数据{get;set;}
}
[DataContract(Namespace=Namespace.ProjectNamespace)]
内部类CSubData:ModuleData
{
[数据成员]
公共字符串名称{get;se
class Program
{
    static void Main(string[] args)
    {
        new TestClass().Test();
    }
}

class TestClass
{
    public virtual void Test()
    {
        // new project object
        var project1 = new Project()
        {
            Data = new List<ModuleData>()
            {
                new AData() { A = "A" },
                new BData() { B = "B" },
                new CData() { C = "C" }
            }
        };

        // serialization; make CData explicitly known to simulate presence of "module C"
        var extraTypes = new[] { typeof(CData) };
        var extraTypesDummy = new[] { typeof(Dummies.CData) };

        var xml = project1.SerializeXml(extraTypes);

        ConsoleAndDebug.WriteLine(xml);

        // Demonstrate that the XML can be deserialized with the dummy CData type.
        TestDeserialize(project1, xml, extraTypesDummy);

        // Demonstrate that the XML can be deserialized with the real CData type.
        TestDeserialize(project1, xml, extraTypes);

        try
        {
            // Demonstrate that the XML cannot be deserialized without either the dummy or real type.
            TestDeserialize(project1, xml, new Type[0]);
            Assert.IsTrue(false);
        }
        catch (AssertionFailedException ex)
        {
            Console.WriteLine("Caught unexpected exception: ");
            Console.WriteLine(ex);
            throw;
        }
        catch (Exception ex)
        {
            ConsoleAndDebug.WriteLine(string.Format("Caught expected exception: {0}", ex.Message));
        }
    }

    public void TestDeserialize<TProject>(TProject project, string xml, Type[] extraTypes)
    {
        TestDeserialize<TProject>(xml, extraTypes);
    }

    public void TestDeserialize<TProject>(string xml, Type[] extraTypes)
    {
        var project2 = xml.DeserializeXml<TProject>(extraTypes);

        var xml2 = project2.SerializeXml(extraTypes);

        ConsoleAndDebug.WriteLine(xml2);

        // Assert that the incoming and re-serialized XML are equivalent (no data was lost).
        Assert.IsTrue(XNode.DeepEquals(XElement.Parse(xml), XElement.Parse(xml2)));
    }
}

public static partial class DataContractSerializerHelper
{
    public static string SerializeXml<T>(this T obj, Type [] extraTypes)
    {
        return obj.SerializeXml(new DataContractSerializer(obj == null ? typeof(T) : obj.GetType(), extraTypes));
    }

    public static string SerializeXml<T>(this T obj, DataContractSerializer serializer)
    {
        serializer = serializer ?? new DataContractSerializer(obj == null ? typeof(T) : obj.GetType());
        using (var textWriter = new StringWriter())
        {
            var settings = new XmlWriterSettings { Indent = true };
            using (var xmlWriter = XmlWriter.Create(textWriter, settings))
            {
                serializer.WriteObject(xmlWriter, obj);
            }
            return textWriter.ToString();
        }
    }

    public static T DeserializeXml<T>(this string xml, Type[] extraTypes)
    {
        return xml.DeserializeXml<T>(new DataContractSerializer(typeof(T), extraTypes));
    }

    public static T DeserializeXml<T>(this string xml, DataContractSerializer serializer)
    {
        using (var textReader = new StringReader(xml ?? ""))
        using (var xmlReader = XmlReader.Create(textReader))
        {
            return (T)(serializer ?? new DataContractSerializer(typeof(T))).ReadObject(xmlReader);
        }
    }
}

public static class ConsoleAndDebug
{
    public static void WriteLine(object s)
    {
        Console.WriteLine(s);
        Debug.WriteLine(s);
    }
}

public class AssertionFailedException : System.Exception
{
    public AssertionFailedException() : base() { }

    public AssertionFailedException(string s) : base(s) { }
}

public static class Assert
{
    public static void IsTrue(bool value)
    {
        if (value == false)
            throw new AssertionFailedException("failed");
    }
}
private Type CreateDummyType(string typeName, string typeNamespace)
{
    var className = $"DummyClass_{random_.Next()}";
    var code = $"[System.Runtime.Serialization.DataContract(Name=\"{typeName}\", Namespace=\"{typeNamespace}\")] public class {className} : ModuleData {{}}";

    using (var provider = new CSharpCodeProvider())
    {
        var parameters = new CompilerParameters();
        parameters.ReferencedAssemblies.Add("System.Runtime.Serialization.dll");
        parameters.ReferencedAssemblies.Add(GetType().Assembly.Location); // this assembly (for ModuleData)

        var results = provider.CompileAssemblyFromSource(parameters, code);
        return results.CompiledAssembly.GetType(className);
    }
}
using System;
using System.IO;
using System.Collections.Generic;
using System.Runtime.Serialization;
using System.Diagnostics;
using System.Xml;
using System.Xml.Linq;
using Microsoft.CSharp;
using System.CodeDom.Compiler;

public static class Namespaces
{
    public const string BaseNamespace = "http://www.Question45412824.com";
    public const string ProjectNamespace = BaseNamespace + "/Project";
    public const string ExtensionNamespace = BaseNamespace + "/Extension";
}

// common base class
[DataContract(Namespace = Namespaces.ProjectNamespace)]
public class ModuleData : IExtensibleDataObject
{
    public ExtensionDataObject ExtensionData { get; set; }
}

[DataContract(Namespace = Namespaces.ProjectNamespace)]
public class AData : ModuleData
{
    [DataMember]
    public string A { get; set; }
}

[DataContract(Namespace = Namespaces.ProjectNamespace)]
public class BData : ModuleData
{
    [DataMember]
    public string B { get; set; }
}

[DataContract(Namespace = Namespaces.ProjectNamespace)]
[KnownType(typeof(AData))]
[KnownType(typeof(BData))]
public class Project
{
    [DataMember]
    public List<ModuleData> Data { get; set; }
}

[DataContract(Namespace = Namespaces.ProjectNamespace)]
internal class CSubData : ModuleData
{
    [DataMember]
    public string Name { get; set; }
}


[DataContract(Namespace = Namespaces.ExtensionNamespace)]
public class CData : ModuleData
{
    [DataMember]
    public ModuleData C { get; set; }
}

class Program
{
    static void Main(string[] args)
    {
        new TestClass().Test();
    }
}

class TestClass
{
    public virtual void Test()
    {
        // new project object
        var project1 = new Project()
        {
            Data = new List<ModuleData>()
                {
                     new AData() { A = "A" },
                     new BData() { B = "B" },
                     new CData() { C = new CSubData() { Name = "C" } }
                }
        };

        // serialization; make CData explicitly known to simulate presence of "module C"
        var extraTypes = new[] { typeof(CData), typeof(CSubData) };

        ConsoleAndDebug.WriteLine("\n== Serialization with all types known ==");
        var xml = project1.SerializeXml(extraTypes);
        ConsoleAndDebug.WriteLine(xml);

        ConsoleAndDebug.WriteLine("\n== Deserialization and subsequent serialization WITH generic resolver and unknown types ==");
        TestDeserialize(project1, xml, new GenericDataContractResolver());

        ConsoleAndDebug.WriteLine("\n== Deserialization and subsequent serialization WITHOUT generic resolver and unknown types ==");
        try
        {
            // Demonstrate that the XML cannot be deserialized without the generic resolver.
            TestDeserialize(project1, xml, new Type[0]);
            Assert.IsTrue(false);
        }
        catch (AssertionFailedException ex)
        {
            Console.WriteLine("Caught unexpected exception: ");
            Console.WriteLine(ex);
            throw;
        }
        catch (Exception ex)
        {
            ConsoleAndDebug.WriteLine(string.Format("Caught expected exception: {0}", ex.Message));
        }
    }

    public void TestDeserialize<TProject>(TProject project, string xml, Type[] extraTypes)
    {
        TestDeserialize<TProject>(xml, extraTypes);
    }

    public void TestDeserialize<TProject>(string xml, Type[] extraTypes)
    {
        var project2 = xml.DeserializeXml<TProject>(extraTypes);

        var xml2 = project2.SerializeXml(extraTypes);

        ConsoleAndDebug.WriteLine(xml2);

        // Assert that the incoming and re-serialized XML are equivalent (no data was lost).
        Assert.IsTrue(XNode.DeepEquals(XElement.Parse(xml), XElement.Parse(xml2)));
    }

    public void TestDeserialize<TProject>(TProject project, string xml, DataContractResolver resolver)
    {
        TestDeserialize<TProject>(xml, resolver);
    }

    public void TestDeserialize<TProject>(string xml, DataContractResolver resolver)
    {
        var project2 = xml.DeserializeXml<TProject>(resolver);

        var xml2 = project2.SerializeXml(resolver);

        ConsoleAndDebug.WriteLine(xml2);

        // Assert that the incoming and re-serialized XML are equivalent (no data was lost).
        Assert.IsTrue(XNode.DeepEquals(XElement.Parse(xml), XElement.Parse(xml2)));
    }
}

public static partial class DataContractSerializerHelper
{
    public static string SerializeXml<T>(this T obj, Type[] extraTypes)
    {
        return obj.SerializeXml(new DataContractSerializer(obj == null ? typeof(T) : obj.GetType(), extraTypes));
    }

    public static string SerializeXml<T>(this T obj, DataContractResolver resolver)
    {
        return obj.SerializeXml(new DataContractSerializer(obj == null ? typeof(T) : obj.GetType(), null, int.MaxValue, false, false, null, resolver));
    }

    public static string SerializeXml<T>(this T obj, DataContractSerializer serializer)
    {
        serializer = serializer ?? new DataContractSerializer(obj == null ? typeof(T) : obj.GetType());
        using (var textWriter = new StringWriter())
        {
            var settings = new XmlWriterSettings { Indent = true };
            using (var xmlWriter = XmlWriter.Create(textWriter, settings))
            {
                serializer.WriteObject(xmlWriter, obj);
            }
            return textWriter.ToString();
        }
    }

    public static T DeserializeXml<T>(this string xml, DataContractResolver resolver)
    {
        return xml.DeserializeXml<T>(new DataContractSerializer(typeof(T), null, int.MaxValue, false, false, null, resolver));
    }

    public static T DeserializeXml<T>(this string xml, Type[] extraTypes)
    {
        return xml.DeserializeXml<T>(new DataContractSerializer(typeof(T), extraTypes));
    }

    public static T DeserializeXml<T>(this string xml, DataContractSerializer serializer)
    {
        using (var textReader = new StringReader(xml ?? ""))
        using (var xmlReader = XmlReader.Create(textReader))
        {
            return (T)(serializer ?? new DataContractSerializer(typeof(T))).ReadObject(xmlReader);
        }
    }
}

public static class ConsoleAndDebug
{
    public static void WriteLine(object s)
    {
        Console.WriteLine(s);
        Debug.WriteLine(s);
    }
}

public class AssertionFailedException : System.Exception
{
    public AssertionFailedException() : base() { }

    public AssertionFailedException(string s) : base(s) { }
}

public static class Assert
{
    public static void IsTrue(bool value)
    {
        if (value == false)
            throw new AssertionFailedException("failed");
    }
}

class GenericDataContractResolver : DataContractResolver
{
    private static readonly Random random_ = new Random();
    private static readonly Dictionary<Tuple<string, string>, Type> toType_ = new Dictionary<Tuple<string, string>, Type>();
    private static readonly Dictionary<Type, Tuple<string, string>> fromType_ = new Dictionary<Type, Tuple<string, string>>();

    private Type CreateDummyType(string typeName, string typeNamespace)
    {
        var className = $"DummyClass_{random_.Next()}";
        var code = $"[System.Runtime.Serialization.DataContract(Name=\"{typeName}\", Namespace=\"{typeNamespace}\")] public class {className} : ModuleData {{}}";

        using (var provider = new CSharpCodeProvider())
        {
            var parameters = new CompilerParameters();
            parameters.ReferencedAssemblies.Add("System.Runtime.Serialization.dll");
            parameters.ReferencedAssemblies.Add(GetType().Assembly.Location); // this assembly (for ModuleData)

            var results = provider.CompileAssemblyFromSource(parameters, code);
            return results.CompiledAssembly.GetType(className);
        }
    }

    // Used at deserialization; allows users to map xsi:type name to any Type 
    public override Type ResolveName(string typeName, string typeNamespace, Type declaredType, DataContractResolver knownTypeResolver)
    {
        var type = knownTypeResolver.ResolveName(typeName, typeNamespace, declaredType, null);

        // resolve all unknown extension datasets; all other should be explicitly known.
        if (type == null && declaredType == typeof(ModuleData) && typeNamespace == Namespaces.ExtensionNamespace)
        {
            // if we already have this type cached, then return the cached one
            var typeNameAndNamespace = new Tuple<string, string>(typeName, typeNamespace);
            if (toType_.TryGetValue(typeNameAndNamespace, out type))
                return type;

            // else compile the dummy type and remember it in the cache
            type = CreateDummyType(typeName, typeNamespace);
            toType_.Add(typeNameAndNamespace, type);
            fromType_.Add(type, typeNameAndNamespace);
        }

        return type;
    }

    // Used at serialization; maps any Type to a new xsi:type representation
    public override bool TryResolveType(Type type, Type declaredType, DataContractResolver knownTypeResolver, out XmlDictionaryString typeName, out XmlDictionaryString typeNamespace)
    {
        if (knownTypeResolver.TryResolveType(type, declaredType, null, out typeName, out typeNamespace))
            return true; // known type

        // is the type one of our cached dummies?
        var typeNameAndNamespace = default(Tuple<string, string>);
        if (declaredType == typeof(ModuleData) && fromType_.TryGetValue(type, out typeNameAndNamespace))
        {
            typeName = new XmlDictionaryString(XmlDictionary.Empty, typeNameAndNamespace.Item1, 0);
            typeNamespace = new XmlDictionaryString(XmlDictionary.Empty, typeNameAndNamespace.Item2, 0);
            return true; // dummy type
        }

        return false; // unknown type
    }
}