在.NET中是否有一个可序列化的通用键/值对类?

82

我正在寻找一个键/值对对象,可以在Web服务中使用。

我尝试使用.NET的System.Collections.Generic.KeyValuePair<>类,但它在Web服务中无法正确序列化。在Web服务中,Key和Value属性未被序列化,使得这个类没用了,除非有人知道如何解决这个问题。

是否有其他通用类可用于此情况?

我想使用.NET的System.Web.UI.Pair类,但它使用Object作为其类型。如果只是为了类型安全,使用通用类会更好。

10个回答

96

只需定义一个结构体/类。

[Serializable]
public struct KeyValuePair<K,V>
{
  public K Key {get;set;}
  public V Value {get;set;}
}

3
了解值类型如何进行哈希和比较相等性是必要的。 - leppie
3
IDictionary现在可以序列化,至少在4.5版本中(使用JSON)。 - tomg
@Joe:随意编写你自己的构造函数。 - leppie
@leppie 我看了,但这只是对这个非常好的答案的一点观察。 - Joe
尝试后,我遇到了构建错误。请提供任何修复的想法。 'AttributeCollection'不包含名称为'Where'的定义,并且最佳扩展方法重载'Queryable.Where<KeyValuePair<string, object>>(IQueryable<KeyValuePair<string, object>>, Expression<Func<KeyValuePair<string, object>, bool>>)'需要类型为'IQueryable<KeyValuePair<string, object>>'的接收器。 - Karthik

22

我认为没有Dictionary<>本身不可XML序列化的问题,当我需要通过Web服务发送字典对象时,我最终自己包装了Dictionary<>对象并添加了对IXMLSerializable的支持。

/// <summary>
/// Represents an XML serializable collection of keys and values.
/// </summary>
/// <typeparam name="TKey">The type of the keys in the dictionary.</typeparam>
/// <typeparam name="TValue">The type of the values in the dictionary.</typeparam>
[XmlRoot("dictionary")]
public class SerializableDictionary<TKey, TValue> : Dictionary<TKey, TValue>, IXmlSerializable
{
    #region Constants

    /// <summary>
    /// The default XML tag name for an item.
    /// </summary>
    private const string DEFAULT_ITEM_TAG = "Item";

    /// <summary>
    /// The default XML tag name for a key.
    /// </summary>
    private const string DEFAULT_KEY_TAG = "Key";

    /// <summary>
    /// The default XML tag name for a value.
    /// </summary>
    private const string DEFAULT_VALUE_TAG = "Value";

    #endregion

    #region Protected Properties

    /// <summary>
    /// Gets the XML tag name for an item.
    /// </summary>
    protected virtual string ItemTagName
    {
        get
        {
            return DEFAULT_ITEM_TAG;
        }
    }

    /// <summary>
    /// Gets the XML tag name for a key.
    /// </summary>
    protected virtual string KeyTagName
    {
        get
        {
            return DEFAULT_KEY_TAG;
        }
    }

    /// <summary>
    /// Gets the XML tag name for a value.
    /// </summary>
    protected virtual string ValueTagName
    {
        get
        {
            return DEFAULT_VALUE_TAG;
        }
    }

    #endregion

    #region Public Methods

    /// <summary>
    /// Gets the XML schema for the XML serialization.
    /// </summary>
    /// <returns>An XML schema for the serialized object.</returns>
    public XmlSchema GetSchema()
    {
        return null;
    }

    /// <summary>
    /// Deserializes the object from XML.
    /// </summary>
    /// <param name="reader">The XML representation of the object.</param>
    public void ReadXml(XmlReader reader)
    {
        XmlSerializer keySerializer = new XmlSerializer(typeof(TKey));
        XmlSerializer valueSerializer = new XmlSerializer(typeof(TValue));

        bool wasEmpty = reader.IsEmptyElement;

        reader.Read();

        if (wasEmpty)
        {
            return;
        }

        while (reader.NodeType != XmlNodeType.EndElement)
        {
            reader.ReadStartElement(ItemTagName);

            reader.ReadStartElement(KeyTagName);
            TKey key = (TKey)keySerializer.Deserialize(reader);
            reader.ReadEndElement();

            reader.ReadStartElement(ValueTagName);
            TValue value = (TValue)valueSerializer.Deserialize(reader);
            reader.ReadEndElement();

            this.Add(key, value);

            reader.ReadEndElement();
            reader.MoveToContent();
        }

        reader.ReadEndElement();
    }

    /// <summary>
    /// Serializes this instance to XML.
    /// </summary>
    /// <param name="writer">The writer to serialize to.</param>
    public void WriteXml(XmlWriter writer)
    {
        XmlSerializer keySerializer = new XmlSerializer(typeof(TKey));
        XmlSerializer valueSerializer = new XmlSerializer(typeof(TValue));

        foreach (TKey key in this.Keys)
        {
            writer.WriteStartElement(ItemTagName);

            writer.WriteStartElement(KeyTagName);
            keySerializer.Serialize(writer, key);
            writer.WriteEndElement();

            writer.WriteStartElement(ValueTagName);
            TValue value = this[key];
            valueSerializer.Serialize(writer, value);
            writer.WriteEndElement();

            writer.WriteEndElement();
        }
    }

    #endregion
}

5
OP并没有提到字典。这个问题是关于序列化键值对的。你的回答与此相关,但我认为它会减弱基本问题的重点。 - Adam Ralph

17
你可以在MSDN博客文章中找到为什么KeyValuePairs无法序列化的原因。
Struct答案是最简单的解决方案,但不是唯一的解决方案。一个更好的解决方案是编写一个可序列化的自定义KeyValuePair类。

9
请注意,DataContractSerializer(随 .NET 3.0 和 WCF 一起提供)可以完美处理KeyValuePair<,>。 因此,这不是一个普遍的序列化问题,而是您使用的特定序列化程序的问题(正如链接到MSDN页面所示)。 - Christian.K
您的 MSDN 博客文章(https://blogs.msdn.microsoft.com/seshadripv/archive/2005/11/02/488273.aspx)现在已经失效。 - brewmanz

8
 [Serializable]
 public class SerializableKeyValuePair<TKey, TValue>
    {

        public SerializableKeyValuePair()
        {
        }

        public SerializableKeyValuePair(TKey key, TValue value)
        {
            Key = key;
            Value = value;
        }

        public TKey Key { get; set; }
        public TValue Value { get; set; }

    }

2
在4.0框架中,还增加了可序列化和可比较的Tuple类族。您可以使用 Tuple.Create(a, b)new Tuple<T1, T2>(a, b)

17
元组类型虽然可序列化,但遗憾的是它们无法进行 XML 序列化。 - Cheetah

0
使用DataContractSerializer,因为它可以处理键值对。
    public static string GetXMLStringFromDataContract(object contractEntity)
    {
        using (System.IO.MemoryStream writer = new System.IO.MemoryStream())
        {
            var dataContractSerializer = new DataContractSerializer(contractEntity.GetType());
            dataContractSerializer.WriteObject(writer, contractEntity);
            writer.Position = 0;
            var streamReader = new System.IO.StreamReader(writer);
            return streamReader.ReadToEnd();
        }
    }

0

DataTable 是我最喜欢的集合,用于包装数据以便序列化为 JSON,因为它易于扩展,无需额外的 struct 并且可以作为可序列化的替代 Tuple<>[]

也许不是最干净的方法,但我更喜欢在类中直接包含和使用它(将被序列化),而不是声明一个新的 struct

class AnyClassToBeSerialized
{
    public DataTable KeyValuePairs { get; }

    public AnyClassToBeSerialized
    {
        KeyValuePairs = new DataTable();
        KeyValuePairs.Columns.Add("Key", typeof(string));
        KeyValuePairs.Columns.Add("Value", typeof(string));
    }

    public void AddEntry(string key, string value)
    {
        DataRow row = KeyValuePairs.NewRow();
        row["Key"] = key; // "Key" & "Value" used only for example
        row["Value"] = value;
        KeyValuePairs.Rows.Add(row);
    }
}

0

0
一个 KeyedCollection 是一种字典类型,可以直接序列化为 XML 而不需要任何废话。唯一的问题是您必须通过 coll["key"].Value 访问值。

我认为KeyedCollection不能在WebService中序列化,因为它没有任何公共构造函数。[Serializable]属性仅适用于远程处理。 - Martin

-3

16
这个已经有人提出过了(已经建议过)。不幸的是,Tuple类不能进行XML序列化。 - Dan Herbert

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接