12

私のアプリはストリーム内のオブジェクトをシリアル化します。これが私が必要とするもののサンプルです:

<links>
  <link href="/users" rel="users" />
  <link href="/features" rel="features" />
</links>

この場合、オブジェクトは「リンク」オブジェクトのコレクションです。

-----------最初のバージョン

最初はDataContractSerializerを使用しましたが、メンバーを属性としてシリアル化することはできません(ソース

これがオブジェクトです:

[DataContract(Name="link")]
public class LinkV1
{
    [DataMember(Name="href")]
    public string Url { get; set; }

    [DataMember(Name="rel")]
    public string Relationship { get; set; }
}

そしてここに結果があります:

<ArrayOflink xmlns:i="...." xmlns="...">
  <link>
    <href>/users</href>
    <rel>users</rel>
  </link>
  <link>
    <href>/features</href>
    <rel>features</rel>
  </link>
</ArrayOflink>

-----------2番目のバージョン

わかりました、私が望むものを静かにしないので、私は古典的なXmlSerializerを試しました、しかし...ああ、ルート要素がコレクションである場合、ルート要素とコレクションの要素の名前を指定することはできません...

コードは次のとおりです。

[XmlRoot("link")]
public class LinkV2
{
    [XmlAttribute("href")]
    public string Url { get; set; }

    [XmlAttribute("rel")]
    public string Relationship { get; set; }
}

結果は次のとおりです。

<ArrayOfLinkV2>
  <LinkV2 href="/users" rel="users" />
  <LinkV2 href="/features" rel="features" />
  <LinkV2 href="/features/user/{keyUser}" rel="featuresByUser" />
</ArrayOfLinkV2>

-----------第3バージョン

XmlSerializer +ルート要素を使用する:

[XmlRoot("trick")]
public class TotallyUselessClass
{
    [XmlArray("links"), XmlArrayItem("link")]
    public List<LinkV2> Links { get; set; }
}

そしてその結果:

 <trick>
  <links>
    <link href="/users" rel="users" />
    <link href="/features" rel="features" />
    <link href="/features/user/{keyUser}" rel="featuresByUser" />
  </links>
</trick>

いいですが、そのルートノードは必要ありません!! コレクションをルートノードにします。

ここに制約があります:

  • シリアル化コードはジェネリックであり、シリアル化可能なものなら何でも機能します
  • 逆演算(逆シリアル化)も機能する必要があります
  • 結果を正規表現したくない(出力ストリームで直接シリアル化する)

今の私の解決策は何ですか:

  1. 自分のXmlSerializerをコーディングする
  2. コレクションで動作するときにXmlSerializerをトリックします(XmlRootElementを見つけて、それを複数化して独自のXmlRootAttributeを生成するようにしましたが、逆シリアル化するときに問題が発生します+アイテム名はクラス名を保持します)

何か案が ?

その問題で本当に気になるのは、私が欲しいものは本当に本当に本当にシンプルに見えるということです...

4

3 に答える 3

9

わかりました。これが私の最終的な解決策です(誰かに役立つことを願っています)。これは、プレーン配列、List <>、HashSet <>、...をシリアル化できます。

これを実現するには、使用するルートノードをシリアライザーに指示する必要がありますが、これはちょっと注意が必要です...

1)シリアル化可能なオブジェクトで「XmlType」を使用します

[XmlType("link")]
public class LinkFinalVersion
{
    [XmlAttribute("href")]
    public string Url { get; set; }

    [XmlAttribute("rel")]
    public string Relationship { get; set; }
}

2)XmlRootAttributeを返す「smart-root-detector-for-collection」メソッドをコーディングします

private XmlRootAttribute XmlRootForCollection(Type type)
{
    XmlRootAttribute result = null;

    Type typeInner = null;
    if(type.IsGenericType)
    {
        var typeGeneric = type.GetGenericArguments()[0];
        var typeCollection = typeof (ICollection<>).MakeGenericType(typeGeneric);
        if(typeCollection.IsAssignableFrom(type))
            typeInner = typeGeneric;
    }
    else if(typeof (ICollection).IsAssignableFrom(type)
        && type.HasElementType)
    {
        typeInner = type.GetElementType();
    }

    // yeepeeh ! if we are working with a collection
    if(typeInner != null)
    {
        var attributes = typeInner.GetCustomAttributes(typeof (XmlTypeAttribute), true);
        if((attributes != null)
            && (attributes.Length > 0))
        {
            var typeName = (attributes[0] as XmlTypeAttribute).TypeName + 's';
            result = new XmlRootAttribute(typeName);
        }
    }
    return result;
}

3)そのXmlRootAttributeをシリアライザーにプッシュします

// hack : get the XmlRootAttribute if the item is a collection
var root = XmlRootForCollection(type);
// create the serializer
var serializer = new XmlSerializer(type, root);

トリッキーだと言った;)


これを改善するには、次のことができます。

A)XmlTypeInCollectionAttributeを作成して、カスタムルート名を指定します(基本的な複数形がニーズに合わない場合)

[XmlType("link")]
[XmlTypeInCollection("links")]
public class LinkFinalVersion
{
}

B)可能であれば、XmlSerializerをキャッシュします(たとえば、静的ディクショナリに)。

私のテストでは、XmlRootAttributesなしでXmlSerializerをインスタンス化するには3ミリ秒かかります。XmlRootAttributeを指定すると、約80ミリ秒かかります(カスタムルートノード名を付けるだけです!)

于 2012-08-03T11:16:38.647 に答える
4

XmlSerializerは必要なことを実行できるはずですが、初期構造とセットアップに大きく依存します。私はそれを自分のコードで使用して、非常によく似たものを生成します。

public class Links<Link> : BaseArrayClass<Link> //use whatever base collection extension you actually need here
{
    //...stuff...//
}

public class Link
{
    [XmlAttribute("href")]
    public string Url { get; set; }

    [XmlAttribute("rel")]
    public string Relationship { get; set; }
}

これで、クラスをシリアル化するLinksと、探しているものが正確に生成されます。

XmlSerializerの問題は、ジェネリックを指定すると、ジェネリックで応答することです。implemets配列をどこかにリストすると、シリアル化された結果はほとんどの場合になりますArrayOf<X>。これを回避するには、プロパティまたはクラスルートに名前を付けることができます。必要なものに近いのは、おそらく例の2番目のバージョンです。オブジェクトのリストリンクの直接シリアル化を試みたと仮定します。ルートノードを指定しなかったため、これは機能しません。さて、同様のアプローチがここにあります。これでは、シリアライザーを宣言するときにXmlRootAttributeを指定します。あなたはこのようになります:

XmlSerializer xs = new XmlSerializer(typeof(List<Link>), new XmlRootAttribute("Links"));
于 2012-08-02T17:31:05.990 に答える
1

どうぞ...

 class Program
{
    static void Main(string[] args)
    {

        Links ls = new Links();
        ls.Link.Add(new Link() { Name = "Mike", Url = "www.xml.com" });
        ls.Link.Add(new Link() { Name = "Jim", Url = "www.xml.com" });
        ls.Link.Add(new Link() { Name = "Peter", Url = "www.xml.com" });

        XmlSerializer xmlSerializer = new XmlSerializer(typeof(Links));

        StringWriter stringWriter = new StringWriter();

        xmlSerializer.Serialize(stringWriter, ls);

        string serializedXML = stringWriter.ToString();

        Console.WriteLine(serializedXML);

        Console.ReadLine();
    }
}

[XmlRoot("Links")]
public class Links
{
    public Links()
    {
        Link = new List<Link>();
    }

    [XmlElement]
    public List<Link> Link { get; set; }
}

[XmlType("Link")]
public class Link
{
    [XmlAttribute("Name")]
    public string Name { get; set; }


    [XmlAttribute("Href")]
    public string Url { get; set; }

}
于 2012-08-02T21:15:10.960 に答える