Czy istnieje możliwość serializacji ogólnej pary klucz / wartość w .NET?

79

Szukam obiektu pary klucz / wartość, który mogę dołączyć do usługi internetowej.

Próbowałem użyć System.Collections.Generic.KeyValuePair<>klasy .NET , ale nie jest ona poprawnie serializowana w usłudze sieciowej. W usłudze sieci Web właściwości klucza i wartości nie są serializowane, co sprawia, że ​​ta klasa jest bezużyteczna, chyba że ktoś zna sposób, aby to naprawić.

Czy istnieje inna klasa ogólna, której można użyć w tej sytuacji?

Użyłbym System.Web.UI.Pairklasy .NET , ale używa ona Object dla swoich typów. Byłoby miło użyć klasy Generic, choćby ze względu na bezpieczeństwo typów.

Dan Herbert
źródło

Odpowiedzi:

95

Po prostu zdefiniuj strukturę / klasę.

[Serializable]
public struct KeyValuePair<K,V>
{
  public K Key {get;set;}
  public V Value {get;set;}
}
leppie
źródło
3
@Paddy: Wiedząc, jak valuetypes zostanie zakodowane i porównywania równości jest koniecznością
leppie
2
IDictionary można teraz serializować w wersji 4.5 (przynajmniej z JSON)
tomg
@Joe: Zapraszam do napisania własnego konstruktora.
leppie
@leppie Zrobiłem, ale tylko obserwacja na temat tej świetnej odpowiedzi.
Joe
Po wypróbowaniu tego otrzymuję ten błąd kompilacji. Podaj pomysły do ​​rozwiązania. '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

Wydaje mi się, że nie ma, ponieważ Dictionary<>sam w sobie nie jest możliwy do serializacji XML, kiedy musiałem wysłać obiekt słownika za pośrednictwem usługi sieciowej, w końcu sam zawinąłem Dictionary<>obiekt i dodałem obsługę 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
}
Skompiluj to
źródło
5
OP w ogóle nie wspomina o słowniku. Pytanie dotyczy serializacji pary klucz / wartość. Twoja odpowiedź jest powiązana, ale myślę, że umniejsza to podstawową kwestię.
Adam Ralph
17

W tym poście na blogu MSDN znajdziesz powód, dla którego nie można serializować par kluczy KeyValuePairs

Odpowiedź Struct jest najprostszym rozwiązaniem, ale nie jedynym rozwiązaniem. „Lepszym” rozwiązaniem jest napisanie klasy Custom KeyValurPair z możliwością serializacji.

user56931
źródło
9
Należy zauważyć, że DataContractSerializer (tak jak jest dostarczany z .NET 3.0 i WCF) może doskonale obsługiwać KeyValuePair <,>. Nie jest to więc ogólny problem z serializacją, ale raczej kwestia konkretnego serializatora, którego używasz (jak sugeruje łącze do strony MSDN).
Christian.K
Twój post na blogu MSDN ( blogs.msdn.microsoft.com/seshadripv/archive/2005/11/02/… ) jest teraz martwym linkiem
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
źródło
1

W ramach 4.0 Framework jest również dodana rodzina klas Tuple, które można serializować i wyrównywać. Możesz użyć Tuple.Create(a, b)lub new Tuple<T1, T2>(a, b).

Peter Oehlert
źródło
16
Chociaż typy krotki można serializować, niestety nie można ich serializować w formacie XML
Cheetah
0

KeyedCollection to typ słownika, który można bezpośrednio serializować do XML bez żadnych bzdur. Jedynym problemem jest to, że musisz uzyskać dostęp do wartości przez: coll ["klucz"]. Wartość;


źródło
Nie sądzę, aby KeyedCollection można było serializować w ramach usługi WebService, ponieważ nie ma ona żadnego publicznego konstruktora. Atrybut [Serializable] działa tylko w przypadku komunikacji zdalnej.
Martin
0

Użyj DataContractSerializer, ponieważ może obsługiwać parę klucz-wartość.

    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
źródło
0

DataTable jest moją ulubioną kolekcją do (wyłącznie) opakowywania danych, które mają być serializowane do formatu JSON, ponieważ można je łatwo rozszerzyć bez potrzeby dodatkowego struct i działa jak serializowalny zamiennik dlaTuple<>[]

Może nie jest to najczystszy sposób, ale wolę włączać i używać go bezpośrednio w klasach (które mają być serializowane), zamiast deklarować nowy 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
źródło
-3

Możesz użyć Tuple<string,object>

zobacz to, aby uzyskać więcej informacji na temat Tupleużycia: Praca z krotką w C # 4.0

Saraf Talukder
źródło
16
To już było sugerowane . Niestety klasa Tuple nie umożliwia serializacji XML.
Dan Herbert