protobuf-net:空のリストのシリアル化
-
24-09-2019 - |
質問
空のリストのシリアル化にはいくつかの問題があります。ここでは、CF 2.0を使用した.NETのいくつかのコード
//Generating the protobuf-msg
ProtoBufMessage msg = new ProtoBufMessage();
msg.list = new List<AnotherProtobufMessage>();
// Serializing and sending throw HTTP-POST
MemoryStream stream = new MemoryStream();
Serializer.Serialize(stream, msg);
byte[] bytes = stream.ToArray();
HttpWebRequest request = createRequest();
request.ContentLength = bytes.Length ;
using (Stream httpStream = request.GetRequestStream())
{
httpStream.Write(bytes, 0, bytes.Length);
}
ストリームに書き込もうとすると、例外が得られました(範囲外の長さ)。ただし、空のリストを持つタイプは0バイトではないようにしてください(タイプインフォメーション?)?
応答には、クライアントのサーバーからのメッセージがあるため、このタイプの送信が必要です。
解決
ワイヤ形式(Googleによって定義されます - 私のコントロールの内側ではありません!)はデータのみを送信します アイテム. 。それはanを区別しません 空の リストとa ヌル リスト。したがって、送信するデータがない場合 - はい、長さは0です(これは非常に質素な形式; P)。
プロトコルバッファーには、ワイヤにタイプメタデータは含まれていません。
ここでのもう1つの一般的なゴッチャは、リストプロパティが自動的に空であると自動的にインスタンス化されていると仮定する可能性があることですが、それはそうではありません(あなたのコードがそれをしない限り、おそらくフィールドイニシャルまたはコンストラクターで)。
これが実行可能なハックです:
[ProtoContract]
class SomeType {
[ProtoMember(1)]
public List<SomeOtherType> Items {get;set;}
[DefaultValue(false), ProtoMember(2)]
private bool IsEmptyList {
get { return Items != null && Items.Count == 0; }
set { if(value) {Items = new List<SomeOtherType>();}}
}
}
多分ハッキーですが、それはうまくいくはずです。あなたも失う可能性があります Items
必要に応じて「セット」して、ドロップするだけです bool
:
[ProtoMember(1)]
public List<SomeOtherType> Items {get {return items;}}
private readonly List<SomeOtherType> items = new List<SomeOtherType>();
[DefaultValue(false), ProtoMember(2)]
private bool IsEmptyList {
get { return items.Count == 0; }
set { }
}
他のヒント
@Marcが言ったように、ワイヤ形式はアイテムのデータのみを送信するため、リストが空またはnullであるかどうかを知るために、その少しの情報をストリームに追加する必要があります。
オリジナルのコレクションが空であるかどうかを示すために追加のプロパティを追加するのは簡単ですが、元のタイプ定義を変更したくない場合は、別の2つのオプションがあります。
代理を使用したシリアル化
代理タイプには追加のプロパティがあり(元のタイプを触れられないようにしてください)、リストの元の状態を復元します:null、アイテムまたは空です。
[TestMethod]
public void SerializeEmptyCollectionUsingSurrogate_RemainEmpty()
{
var instance = new SomeType { Items = new List<int>() };
// set the surrogate
RuntimeTypeModel.Default.Add(typeof(SomeType), true).SetSurrogate(typeof(SomeTypeSurrogate));
// serialize-deserialize using cloning
var clone = Serializer.DeepClone(instance);
// clone is not null and empty
Assert.IsNotNull(clone.Items);
Assert.AreEqual(0, clone.Items.Count);
}
[ProtoContract]
public class SomeType
{
[ProtoMember(1)]
public List<int> Items { get; set; }
}
[ProtoContract]
public class SomeTypeSurrogate
{
[ProtoMember(1)]
public List<int> Items { get; set; }
[ProtoMember(2)]
public bool ItemsIsEmpty { get; set; }
public static implicit operator SomeTypeSurrogate(SomeType value)
{
return value != null
? new SomeTypeSurrogate { Items = value.Items, ItemsIsEmpty = value.Items != null && value.Items.Count == 0 }
: null;
}
public static implicit operator SomeType(SomeTypeSurrogate value)
{
return value != null
? new SomeType { Items = value.ItemsIsEmpty ? new List<int>() : value.Items }
: null;
}
}
タイプを拡張可能にします
protobuf-netは、壊れることなくフィールドをメッセージに追加できるようにタイプを拡張できるiextensibleインターフェイスを提案します(続きを読む ここ)。 protobuf-net拡張機能を使用するために Extensible
クラスまたは実装 IExtensible
継承の制約を回避するためのインターフェイス。
あなたのタイプが「拡張可能」になったので、あなたは定義します [OnSerializing]
と [OnDeserialized]
ストリームにシリアル化され、元の状態でオブジェクトを再構築する際にそこから脱色する新しいインジケーターを追加する方法。
長所は、新しいプロパティや新しいタイプを代理として定義する必要がないということです。 IExtensible
タイプがタイプモデルで定義されているサブタイプがある場合、サポートされていません。
[TestMethod]
public void SerializeEmptyCollectionInExtensibleType_RemainEmpty()
{
var instance = new Store { Products = new List<string>() };
// serialize-deserialize using cloning
var clone = Serializer.DeepClone(instance);
// clone is not null and empty
Assert.IsNotNull(clone.Products);
Assert.AreEqual(0, clone.Products.Count);
}
[ProtoContract]
public class Store : Extensible
{
[ProtoMember(1)]
public List<string> Products { get; set; }
[OnSerializing]
public void OnDeserializing()
{
var productsListIsEmpty = this.Products != null && this.Products.Count == 0;
Extensible.AppendValue(this, 101, productsListIsEmpty);
}
[OnDeserialized]
public void OnDeserialized()
{
var productsListIsEmpty = Extensible.GetValue<bool>(this, 101);
if (productsListIsEmpty)
this.Products = new List<string>();
}
}
public List<NotificationAddress> BccAddresses { get; set; }
あなたは次のように置き換えることができます:
private List<NotificationAddress> _BccAddresses;
public List<NotificationAddress> BccAddresses {
get { return _BccAddresses; }
set { _BccAddresses = (value != null && value.length) ? value : null; }
}