web-dev-qa-db-ja.com

XmlSerializerを使用して内部クラスをシリアル化するにはどうすればよいですか?

サードパーティとのインターフェイスとなるライブラリを構築しています。通信は、XMLおよびHTTPポストを介して行われます。うまくいきました。

ただし、ライブラリを使用するコードはすべて、内部クラスを認識する必要はありません。私の内部オブジェクトは、このメソッドを使用してXMLにシリアル化されます。

internal static string SerializeXML(Object obj)
{
    XmlSerializer serializer = new XmlSerializer(obj.GetType(), "some.domain");

    //settings
    XmlWriterSettings settings = new XmlWriterSettings();
    settings.Indent = true;
    settings.OmitXmlDeclaration = true;

    using (StringWriter stream = new StringWriter())
    {
        using (XmlWriter writer = XmlWriter.Create(stream, settings))
        {
            serializer.Serialize(writer, obj);
        }
        return stream.ToString();
    }
}

ただし、クラスのアクセス修飾子をinternalに変更すると、runtimeで例外が発生します。

[System.InvalidOperationException] = {"MyNamespace.MyClassは保護レベルのためアクセスできません。処理できるのはパブリックタイプのみです。"}

この例外は、上記のコードの最初の行で発生します。

ライブラリのクラスを公開したくないため、公開しないようにしたい。それをしてもいいですか?ジェネリックシリアライザーを使用して、内部型をシリアライズ可能にするにはどうすればよいですか?何が悪いのですか?

28

Sowmy Srinivasanのブログ-XmlSerializerを使用した内部型のシリアル化

内部型をシリアル化できることは、 XmlSerializer チームから見られる一般的な要求の1つです。図書館を出荷する人々からの合理的な要求です。シリアライザーのためだけにXmlSerializer型をパブリックにしたくありません。私は最近、XmlSerializerを作成したチームから、XmlSerializerを使用するチームに移動しました。同様のリクエストに出くわしたとき、「いいえ。使用 DataContractSerializer 」と言いました。

理由は簡単です。 XmlSerializerはコードを生成することによって機能します。生成されたコードは、動的に生成されたアセンブリに存在し、シリアル化される型にアクセスする必要があります。 XmlSerializerは 軽量コード生成 が登場する前に開発されたため、生成されたコードは別のアセンブリのパブリックタイプ以外にはアクセスできません。したがって、シリアル化される型は公開する必要があります。

「 ' InternalsVisibleTo '属性が使用されている場合は、公開する必要はありません」という賢明な読者のささやきを聞きます。

「正しいですが、生成されたアセンブリの名前は事前にわかりません。内部にどのアセンブリを表示しますか?」

正確な読者:「アセンブリ名は、 ' sgen.exe 'を使用した場合にわかります」

私:「sgenが型のシリアライザーを生成するには、それらを公開する必要があります」

正確な読者:「2パスのコンパイルを行うことができます。1つはタイプがパブリックであるsgenのパスで、もう1つはタイプが内部であるシッピングのパスです。

彼らは正しいかもしれません!鋭敏な読者にサンプルを書いてくれるよう頼むと、おそらくこのようなものを書くでしょう。 (免責事項:これは公式のソリューションではありません。YMMV)

using System;
using System.IO;
using System.Xml.Serialization;
using System.Runtime.CompilerServices;
using System.Reflection;

[Assembly: InternalsVisibleTo("Program.XmlSerializers")]

namespace InternalTypesInXmlSerializer
{
    class Program
    {
        static void Main(string[] args)
        {
            Address address = new Address();
            address.Street = "One Microsoft Way";
            address.City = "Redmond";
            address.Zip = 98053;
            Order order = new Order();
            order.BillTo = address;
            order.ShipTo = address;

            XmlSerializer xmlSerializer = GetSerializer(typeof(Order));
            xmlSerializer.Serialize(Console.Out, order);
        }

        static XmlSerializer GetSerializer(Type type)
        {
#if Pass1
            return new XmlSerializer(type);
#else
            Assembly serializersDll = Assembly.Load("Program.XmlSerializers");
            Type xmlSerializerFactoryType = serializersDll.GetType("Microsoft.Xml.Serialization.GeneratedAssembly.XmlSerializerContract");

            MethodInfo getSerializerMethod = xmlSerializerFactoryType.GetMethod("GetSerializer", BindingFlags.Public | BindingFlags.Instance);

            return (XmlSerializer)getSerializerMethod.Invoke(Activator.CreateInstance(xmlSerializerFactoryType), new object[] { type });

#endif
        }
    }

#if Pass1
    public class Address
#else
    internal class Address
#endif
    {
        public string Street;
        public string City;
        public int Zip;
    }

#if Pass1
    public class Order
#else
    internal class Order
#endif
    {
        public Address ShipTo;
        public Address BillTo;
    }
} 

一部の賢明な「ハッキング」リーダーは、それをコンパイルするためのbuild.cmdを提供してくれます。

csc /d:Pass1 program.cs

sgen program.exe

csc program.cs
20
hemp

別の方法として、動的に作成されたパブリッククラスを使用できます(これはサードパーティに公開されません)。

static void Main()
{
    var emailType = CreateEmailType();

    dynamic email = Activator.CreateInstance(emailType);

    email.From = "[email protected]";
    email.To = "[email protected]";
    email.Subject = "Dynamic Type";
    email.Boby = "XmlSerializer can use this!";
}

static Type CreateEmailType()
{
    var assemblyName = new AssemblyName("DynamicAssembly");

    var assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run);

    var moduleBuilder = assemblyBuilder.DefineDynamicModule(assemblyName.Name);

    var typeBuilder = moduleBuilder.DefineType(
        "Email",
        (
            TypeAttributes.Public |
            TypeAttributes.Sealed |
            TypeAttributes.SequentialLayout |
            TypeAttributes.Serializable
        ),
        typeof(ValueType)
    );

    typeBuilder.DefineField("From", typeof(string), FieldAttributes.Public);
    typeBuilder.DefineField("To", typeof(string), FieldAttributes.Public);
    typeBuilder.DefineField("Subject", typeof(string), FieldAttributes.Public);
    typeBuilder.DefineField("Body", typeof(string), FieldAttributes.Public);

    return typeBuilder.CreateType();
}
1
drowa