問題が解決しました!
OK、それで私はついにそこに着きました(確かにここからたくさんの助けを借りて!)。
要約すると:
目標:
- メンテナンスの頭痛の種のため、 XmlIncludeルートを下りたくありませんでした。
- 解決策が見つかったら、他のアプリケーションにすばやく実装できるようにしたかったのです。
- 個々の抽象プロパティだけでなく、抽象型のコレクションも使用できます。
- 具体的な授業で「特別な」ことをしなくてはいけないということは、あまり気になりませんでした。
確認された問題/注意点:
- XmlSerializerはかなりクールなリフレクションを実行しますが、抽象型に関しては非常に制限されています(つまり、サブクラスではなく、抽象型自体のインスタンスでのみ機能します)。
- Xml属性デコレータは、XmlSerializerが検出したプロパティをどのように処理するかを定義します。物理タイプも指定できますが、これにより、クラスとシリアライザーの間に緊密な結合が作成されます(良くありません)。
- IXmlSerializableを実装するクラスを作成することで、独自のXmlSerializerを実装できます。
ソリューション
ジェネリッククラスを作成しました。このクラスでは、操作する抽象型としてジェネリック型を指定します。これにより、キャストをハードコーディングできるため(つまり、XmlSerializerよりも多くの情報を取得できるため)、クラスは抽象型と具象型の間で「変換」することができます。
次に、 IXmlSerializableインターフェイスを実装しました。これは非常に簡単ですが、シリアル化するときは、具象クラスの型をXMLに書き込む必要があるため、逆シリアル化するときにキャストバックできます。2つのクラスが含まれるアセンブリは異なる可能性があるため、完全に修飾されている必要があることに注意することも重要です。もちろん、ここで行う必要のある小さなタイプチェックなどがあります。
XmlSerializerはキャストできないため、それを行うためのコードを提供する必要があります。そのため、暗黙の演算子がオーバーロードされます(これができるとは思ってもみませんでした!)。
AbstractXmlSerializerのコードは次のとおりです。
using System;
using System.Collections.Generic;
using System.Text;
using System.Xml.Serialization;
namespace Utility.Xml
{
public class AbstractXmlSerializer<AbstractType> : IXmlSerializable
{
// Override the Implicit Conversions Since the XmlSerializer
// Casts to/from the required types implicitly.
public static implicit operator AbstractType(AbstractXmlSerializer<AbstractType> o)
{
return o.Data;
}
public static implicit operator AbstractXmlSerializer<AbstractType>(AbstractType o)
{
return o == null ? null : new AbstractXmlSerializer<AbstractType>(o);
}
private AbstractType _data;
/// <summary>
/// [Concrete] Data to be stored/is stored as XML.
/// </summary>
public AbstractType Data
{
get { return _data; }
set { _data = value; }
}
/// <summary>
/// **DO NOT USE** This is only added to enable XML Serialization.
/// </summary>
/// <remarks>DO NOT USE THIS CONSTRUCTOR</remarks>
public AbstractXmlSerializer()
{
// Default Ctor (Required for Xml Serialization - DO NOT USE)
}
/// <summary>
/// Initialises the Serializer to work with the given data.
/// </summary>
/// <param name="data">Concrete Object of the AbstractType Specified.</param>
public AbstractXmlSerializer(AbstractType data)
{
_data = data;
}
#region IXmlSerializable Members
public System.Xml.Schema.XmlSchema GetSchema()
{
return null; // this is fine as schema is unknown.
}
public void ReadXml(System.Xml.XmlReader reader)
{
// Cast the Data back from the Abstract Type.
string typeAttrib = reader.GetAttribute("type");
// Ensure the Type was Specified
if (typeAttrib == null)
throw new ArgumentNullException("Unable to Read Xml Data for Abstract Type '" + typeof(AbstractType).Name +
"' because no 'type' attribute was specified in the XML.");
Type type = Type.GetType(typeAttrib);
// Check the Type is Found.
if (type == null)
throw new InvalidCastException("Unable to Read Xml Data for Abstract Type '" + typeof(AbstractType).Name +
"' because the type specified in the XML was not found.");
// Check the Type is a Subclass of the AbstractType.
if (!type.IsSubclassOf(typeof(AbstractType)))
throw new InvalidCastException("Unable to Read Xml Data for Abstract Type '" + typeof(AbstractType).Name +
"' because the Type specified in the XML differs ('" + type.Name + "').");
// Read the Data, Deserializing based on the (now known) concrete type.
reader.ReadStartElement();
this.Data = (AbstractType)new
XmlSerializer(type).Deserialize(reader);
reader.ReadEndElement();
}
public void WriteXml(System.Xml.XmlWriter writer)
{
// Write the Type Name to the XML Element as an Attrib and Serialize
Type type = _data.GetType();
// BugFix: Assembly must be FQN since Types can/are external to current.
writer.WriteAttributeString("type", type.AssemblyQualifiedName);
new XmlSerializer(type).Serialize(writer, _data);
}
#endregion
}
}
では、そこから、XmlSerializerにデフォルトではなくシリアライザーを使用するように指示するにはどうすればよいでしょうか。Xml属性typeプロパティ内で型を渡す必要があります。次に例を示します。
[XmlRoot("ClassWithAbstractCollection")]
public class ClassWithAbstractCollection
{
private List<AbstractType> _list;
[XmlArray("ListItems")]
[XmlArrayItem("ListItem", Type = typeof(AbstractXmlSerializer<AbstractType>))]
public List<AbstractType> List
{
get { return _list; }
set { _list = value; }
}
private AbstractType _prop;
[XmlElement("MyProperty", Type=typeof(AbstractXmlSerializer<AbstractType>))]
public AbstractType MyProperty
{
get { return _prop; }
set { _prop = value; }
}
public ClassWithAbstractCollection()
{
_list = new List<AbstractType>();
}
}
ここでは、コレクションと単一のプロパティが公開されていることがわかります。必要なのは、パラメーターという名前の型をXml宣言に追加することだけです。簡単です。:D
注:このコードを使用する場合は、大声でお願いします。また、より多くの人々をコミュニティに呼び込むのにも役立ちます:)
さて、しかし、彼らはすべて賛否両論を持っていたので、ここでの答えをどうするかについてはわかりません。私は有用だと思うものをアップモッドし(そうでないものには不快感はありません)、担当者ができたらこれを閉じます:)
興味深い問題と解決するのが楽しい!:)