web-dev-qa-db-ja.com

WCF:ジェネリックコレクションのシリアル化と逆シリアル化

ジェネリックリストを保持するクラスチームがあります。

[DataContract(Name = "TeamDTO", IsReference = true)]
public class Team
{
    [DataMember]
    private IList<Person> members = new List<Person>();

    public Team()
    {
        Init();
    }

    private void Init()
    {
        members = new List<Person>();
    }

    [System.Runtime.Serialization.OnDeserializing]
    protected void OnDeserializing(StreamingContext ctx)
    {
        Log("OnDeserializing of Team called");
        Init();
        if (members != null) Log(members.ToString());
    }

    [System.Runtime.Serialization.OnSerializing]
    private void OnSerializing(StreamingContext ctx)
    {
        Log("OnSerializing of Team called");
        if (members != null) Log(members.ToString());
    }

    [System.Runtime.Serialization.OnDeserialized]
    protected void OnDeserialized(StreamingContext ctx)
    {
        Log("OnDeserialized of Team called");
        if (members != null) Log(members.ToString());
    }

    [System.Runtime.Serialization.OnSerialized]
    private void OnSerialized(StreamingContext ctx)
    {
        Log("OnSerialized of Team called");
        Log(members.ToString());
    }

このクラスをWCFサービスで使用すると、次のログ出力が得られます

OnSerializing of Team called    
System.Collections.Generic.List 1[XXX.Person]

OnSerialized of Team called    
System.Collections.Generic.List 1[XXX.Person]

OnDeserializing of Team called    
System.Collections.Generic.List 1[XXX.Person]

OnDeserialized of Team called    
XXX.Person[]

デシリアライズ後、membersは配列であり、フィールドタイプはILi​​st <>(?!)ですが、ジェネリックリストではなくなりました。このオブジェクトをWCFサービス経由で返送しようとすると、ログ出力が表示されます。

OnSerializing of Team called
XXX.Person[]

この後、ユニットテストがSystem.ExecutionEngineExceptionでクラッシュします。これは、WCFサービスがアレイをシリアル化できないことを意味します。 (おそらくIList <>を期待していたため)

だから、私の質問は:なぜ私のIList <>のタイプが逆シリアル化後に配列であるのか、そしてなぜそれ以降私のチームオブジェクトをシリアル化できないのか誰かが知っていますか?

ありがとう

20
Fabiano

DataContractSerializerの落とし穴の1つに遭遇しました。

修正:プライベートメンバー宣言を次のように変更します。

[DataMember]
private List<Person> members = new List<Person>();

または、プロパティを次のように変更します。

[DataMember()]
public IList<Person> Feedback {
    get { return m_Feedback; }
    set {
        if ((value != null)) {
            m_Feedback = new List<Person>(value);

        } else {
            m_Feedback = new List<Person>();
        }
    }
}

そしてそれはうまくいくでしょう。 Microsoft Connectのバグは ここ

この問題は、IList<T> DataMemberを使用してオブジェクトを逆シリアル化してから、同じインスタンスを再度シリアル化しようとしたときに発生します。

何かクールなものを見たい場合:

using System;
using System.Collections.Generic;

class TestArrayAncestry
{
    static void Main(string[] args)
    {
        int[] values = new[] { 1, 2, 3 };        
        Console.WriteLine("int[] is IList<int>: {0}", values is IList<int>);
    }
}

int[] is IList<int>: Trueを出力します。

これが、逆シリアル化後に配列として返される理由である可能性がありますが、直感的ではありません。

ただし、配列のIList<int>でAdd()メソッドを呼び出すと、NotSupportedExceptionがスローされます。

それらの.NETの癖の1つ。

22
Leon Breedt

LINQを介してデータベースから読み取ったIListを転送しているときに、このエラーが発生しました。 WCFは、Windows Server 2008x64のIIS 7でホストされていました。

アプリプールは警告なしでクラッシュしました。

[ServiceBehavior]
public class Webservice : IWebservice
{

    public IList<object> GetObjects()
    {
        return Database.Instance.GetObjects();
    }
}

まったく同じ問題ではありませんが、同じ原因がある可能性があります。

の解決策は、MSホットフィックスKB973110をインストールすることでした http://support.Microsoft.com/kb/971030/en-us

2
Fedearne

WCFサービス参照が、既存の型を使用するのではなく、プロキシクラスを作成しているようです。プロキシクラスは単純な配列のみを使用でき、汎用リストのような.NET固有の型は使用できません。

このプロキシクラスの変換を回避するには、[サービス参照の追加]画面で[詳細設定]ボタンをクリックし、[参照されるアセンブリでタイプを再利用する]がオンになっていることを確認します。これにより、オブジェクトをシリアル化および逆シリアル化するときに、既存のクラス(汎用リストを含む)が確実に使用されます。

1
jeremcc

私のブログから直接引用。私はそれが役立つことを願っています:

最近、WCFサービスを使用していて、ASP.netMVCアプリでカスタムモデルバインダーを使用しているという問題が発生しました。 IListsを連載していたときは、すべてうまくいきました。 IListは、デフォルトで配列にシリアル化されます。最終的に、Reflectionを使用して配列をIListsに変換し直し、カスタムモデルバインダーで次のメソッドを呼び出しました。メソッドは次のようになります。

public object ConvertArraysToIList(object returnObj)    
{

if (returnObj != null)
{
    var allProps = returnObj.GetType().GetProperties().Where(p => p.PropertyType.IsPublic 
        && p.PropertyType.IsGenericType 
        && p.PropertyType.Name==typeof(IList<>).Name).ToList();

    foreach (var prop in allProps)
    {
        var value = prop.GetValue(returnObj,null);
        //set the current property to a new instance of the IList<>
        var arr=(System.Array)value; 
        Type listType=null;

        if(arr!=null)
        {
            listType= arr.GetType().GetElementType();
        }

        //create an empty list of the specific type
        var listInstance = typeof(List<>)
          .MakeGenericType(listType)
          .GetConstructor(Type.EmptyTypes)
          .Invoke(null);

        foreach (var currentValue in arr)
        {
            listInstance.GetType().GetMethod("Add").Invoke(listInstance, new[] { currentValue });
        }

        prop.SetValue(returnObj, listInstance, null);
    }
}

return returnObj;
}
1
Abid Rahman