¿Existe una clase de par clave / valor genérica serializable en .NET?

79

Estoy buscando un objeto de par clave / valor que pueda incluir en un servicio web.

Intenté usar la System.Collections.Generic.KeyValuePair<>clase de .NET , pero no se serializa correctamente en un servicio web. En un servicio web, las propiedades Clave y Valor no se serializan, lo que hace que esta clase sea inútil, a menos que alguien sepa cómo solucionarlo.

¿Existe alguna otra clase genérica que pueda usarse para esta situación?

Usaría la System.Web.UI.Pairclase de .NET , pero usa Object para sus tipos. Sería bueno usar una clase Genérica, aunque solo sea por seguridad de tipos.

Dan Herbert
fuente

Respuestas:

95

Simplemente defina una estructura / clase.

[Serializable]
public struct KeyValuePair<K,V>
{
  public K Key {get;set;}
  public V Value {get;set;}
}
leppie
fuente
3
@Paddy: Saber cómo se calculan los tipos de valor y comparar la igualdad es imprescindible
leppie
2
IDictionary es serializable ahora, en 4.5 (al menos con JSON)
TomG
@Joe: Siéntete libre de escribir tu propio constructor.
leppie
@leppie Lo hice, pero solo una observación sobre esta gran respuesta.
Joe
Después de intentar esto, recibo este error de compilación. Proporcione ideas para solucionarlo. 'AttributeCollection' does not contain a definition for 'Where' and the best extension method overload 'Queryable.Where<KeyValuePair<string, object>>(IQueryable<KeyValuePair<string, object>>, Expression<Func<KeyValuePair<string, object>, bool>>)' requires a receiver of type 'IQueryable<KeyValuePair<string, object>>'
Karthik
22

No creo que exista ya que en Dictionary<>sí mismo no es XML serializable, cuando tuve la necesidad de enviar un objeto de diccionario a través de un servicio web, terminé envolviendo el Dictionary<>objeto yo mismo y agregando soporte para 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
}
Compila esto
fuente
5
OP no menciona Diccionario en absoluto. La pregunta es sobre serializar un par clave / valor. Su respuesta está relacionada, pero creo que resta valor al problema fundamental.
Adam Ralph
17

Encontrará la razón por la que KeyValuePairs no se puede serializar en esta publicación de blog de MSDN

La respuesta de Struct es la solución más simple, pero no la única. Una solución "mejor" es escribir una clase KeyValurPair personalizada que sea serializable.

usuario56931
fuente
9
Tenga en cuenta que DataContractSerializer (como viene con .NET 3.0 y WCF) puede manejar perfectamente KeyValuePair <,>. Por lo tanto, no es un problema de serialización general, sino más bien un problema del serializador específico que usa (como sugiere el enlace a la página de MSDN).
Christian.K
Su publicación de blog de MSDN ( blogs.msdn.microsoft.com/seshadripv/archive/2005/11/02/… ) ahora es un vínculo muerto
brewmanz
7
 [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; }

    }
GregoryBrad
fuente
1

En el marco 4.0, también se agrega la familia de clases Tuple que son serializables y equiparables. Puede usar Tuple.Create(a, b)o new Tuple<T1, T2>(a, b).

Peter Oehlert
fuente
16
Si bien los tipos de tupla son serializables, desafortunadamente no son serializables XML
Cheetah
0

Un KeyedCollection es un tipo de diccionario que se puede serializar directamente a xml sin tonterías. El único problema es que debe acceder a los valores mediante: coll ["clave"]. Valor;


fuente
No creo que KeyedCollection pueda serializarse dentro de un WebService porque no tiene ningún constructor público. El atributo [Serializable] sólo funciona para la comunicación remota.
Martin
0

Utilice DataContractSerializer ya que puede manejar el par clave-valor.

    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();
        }
    }
Hasse
fuente
0

DataTablees mi colección favorita para (únicamente) envolver datos para serializarlos en JSON, ya que es fácil de expandir sin la necesidad de un extra structy actúa como un reemplazo serializable paraTuple<>[]

Tal vez no sea la forma más limpia, pero prefiero incluirlo y usarlo directamente en las clases (que se serializarán), en lugar de declarar un nuevo 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);
    }
}
Teodor Tite
fuente
-3

Puedes usar Tuple<string,object>

vea esto para obtener más detalles sobre el Tupleuso: Trabajar con Tuple en C # 4.0

Saraf Talukder
fuente
16
Esto ya fue sugerido . Desafortunadamente, la clase Tuple no se puede serializar en XML.
Dan Herbert