問題が解決しました!
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
{
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;
public AbstractType Data
{
get { return _data; }
set { _data = value; }
}
public AbstractXmlSerializer()
{
}
public AbstractXmlSerializer(AbstractType data)
{
_data = data;
}
#region IXmlSerializable Members
public System.Xml.Schema.XmlSchema GetSchema()
{
return null;
}
public void ReadXml(System.Xml.XmlReader reader)
{
string typeAttrib = reader.GetAttribute("type");
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);
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.");
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 + "').");
reader.ReadStartElement();
this.Data = (AbstractType)new
XmlSerializer(type).Deserialize(reader);
reader.ReadEndElement();
}
public void WriteXml(System.Xml.XmlWriter writer)
{
Type type = _data.GetType();
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
注:このコードを使用する場合は、大声でお願いします。また、より多くの人々をコミュニティに呼び込むのにも役立ちます:)
さて、しかし、彼ら全員が彼らの賛否両論を持っていたので、ここで答えをどうするかについてはわかりません。私は有用だと思うものをアップモッドし(そうでないものには不快感はありません)、担当者ができたらこれを閉じます:)
興味深い問題と解決するのが楽しい!:)