Lớp tuần tự hóa chứa thành viên từ điển


144

Mở rộng theo vấn đề trước đây của tôi , tôi đã quyết định (hủy) tuần tự hóa lớp tệp cấu hình của mình, nó hoạt động rất tốt.

Bây giờ tôi muốn để lưu trữ một mảng kết hợp của các chữ cái ổ đĩa để lập bản đồ (Điều quan trọng là các ký tự ổ đĩa, giá trị là con đường mạng) và đã cố gắng sử dụng Dictionary, HybridDictionaryHashtablecho điều này nhưng tôi luôn luôn nhận được lỗi sau khi gọi ConfigFile.Load()hoặc ConfigFile.Save():

Có một lỗi phản ánh loại 'App.ConfigFile'. [snip] System.NotSupportedException: Không thể tuần tự hóa thành viên App.Configfile.mappedDrive [snip]

Từ những gì tôi đã đọc Từ điển và HashTables có thể được nối tiếp, vậy tôi đang làm gì sai?

[XmlRoot(ElementName="Config")]
public class ConfigFile
{
    public String guiPath { get; set; }
    public string configPath { get; set; }
    public Dictionary<string, string> mappedDrives = new Dictionary<string, string>();

    public Boolean Save(String filename)
    {
        using(var filestream = File.Open(filename, FileMode.OpenOrCreate,FileAccess.ReadWrite))
        {
            try
            {
                var serializer = new XmlSerializer(typeof(ConfigFile));
                serializer.Serialize(filestream, this);
                return true;
            } catch(Exception e) {
                MessageBox.Show(e.Message);
                return false;
            }
        }
    }

    public void addDrive(string drvLetter, string path)
    {
        this.mappedDrives.Add(drvLetter, path);
    }

    public static ConfigFile Load(string filename)
    {
        using (var filestream = File.Open(filename, FileMode.Open, FileAccess.Read))
        {
            try
            {
                var serializer = new XmlSerializer(typeof(ConfigFile));
                return (ConfigFile)serializer.Deserialize(filestream);
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message + ex.ToString());
                return new ConfigFile();
            }
        }
    }
}

Câu trả lời:


77

Bạn không thể tuần tự hóa một lớp thực hiện IDadata. Kiểm tra liên kết này .

Q: Tại sao tôi không thể tuần tự hóa hashtables?

Trả lời: XmlSerializer không thể xử lý các lớp thực hiện giao diện IDadata. Điều này một phần là do các ràng buộc về lịch trình và một phần là do thực tế là một hashtable không có đối tác trong hệ thống loại XSD. Giải pháp duy nhất là triển khai một hashtable tùy chỉnh không thực hiện giao diện IDadata.

Vì vậy, tôi nghĩ rằng bạn cần phải tạo phiên bản Từ điển của riêng bạn cho việc này. Kiểm tra câu hỏi khác này .


4
Chỉ cần tự hỏi các DataContractSerializerlớp có thể làm điều đó. Chỉ là đầu ra là một chút xấu xí.
thiệu

186

Có một giải pháp tại Weblog của Paul Welter - Từ điển chung nối tiếp XML

Vì một số lý do, Từ điển chung trong .net 2.0 không phải là tuần tự hóa XML. Đoạn mã sau đây là một từ điển chung tuần tự xml. Từ điển là serialzable bằng cách thực hiện giao diện IXmlSerializable.

using System;
using System.Collections.Generic;
using System.Text;
using System.Xml.Serialization;

[XmlRoot("dictionary")]
public class SerializableDictionary<TKey, TValue>
    : Dictionary<TKey, TValue>, IXmlSerializable
{
    public SerializableDictionary() { }
    public SerializableDictionary(IDictionary<TKey, TValue> dictionary) : base(dictionary) { }
    public SerializableDictionary(IDictionary<TKey, TValue> dictionary, IEqualityComparer<TKey> comparer) : base(dictionary, comparer) { }
    public SerializableDictionary(IEqualityComparer<TKey> comparer) : base(comparer) { }
    public SerializableDictionary(int capacity) : base(capacity) { }
    public SerializableDictionary(int capacity, IEqualityComparer<TKey> comparer) : base(capacity, comparer) { }

    #region IXmlSerializable Members
    public System.Xml.Schema.XmlSchema GetSchema()
    {
        return null;
    }

    public void ReadXml(System.Xml.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 != System.Xml.XmlNodeType.EndElement)
        {
            reader.ReadStartElement("item");

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

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

            this.Add(key, value);

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

    public void WriteXml(System.Xml.XmlWriter writer)
    {
        XmlSerializer keySerializer = new XmlSerializer(typeof(TKey));
        XmlSerializer valueSerializer = new XmlSerializer(typeof(TValue));

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

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

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

            writer.WriteEndElement();
        }
    }
    #endregion
}

16
+1. Hey, tại sao Stack Overflow không có nút mã sao chép? Hừm? Vì mã này đáng để sao chép!
chập chững

1
+1 câu trả lời tuyệt vời. Cũng hoạt động với SortedList, chỉ cần thay đổi "serializableDixi" thành "serializableSorticList" và "Dictionary <TKey, TValue>" thành "SortedList <TKey, TValue>".
kdm bồ

1
+1 và một đề xuất. Khi một đối tượng serializableDixi chứa nhiều hơn một thành phần, ngoại lệ sẽ bị ném ... ReadXml () và WriteXml () nên được sửa đổi để di chuyển ReadStartEuity ("item"); và WriteStartEuity ("mục"); và ReadEndEuity () và WriteEndEuity () liên quan của nó ra khỏi vòng lặp while.
MNS

1
Điều đó có nghĩa là trong các khung sau này, IDadata có thể tuần tự hóa không?
Thomas

1
Việc triển khai này sẽ hoạt động nếu từ điển đang lưu trữ stringcác giá trị, nhưng sẽ đưa ra một quá trình InvalidOperationExceptionkhử lưu huỳnh đề cập đến một phần tử trình bao bọc bất ngờ nếu bạn thử lưu trữ các đối tượng tùy chỉnh hoặc mảng chuỗi trong đó. (Xem câu hỏi của tôi để biết ví dụ về các vấn đề tôi gặp phải với vấn đề này.)
Christopher Kyle Horton

57

Thay vì sử dụng XmlSerializerbạn có thể sử dụng a System.Runtime.Serialization.DataContractSerializer. Điều này có thể tuần tự từ điển và giao diện không có mồ hôi.

Đây là một liên kết đến một ví dụ đầy đủ, http://theburnmonk.com/2010/05/net-tips-xml-serialize-or-deserialize-dipedia-in-csharp/


2
Câu trả lời hay nhất, thực tế.
DWRoelands

Đồng ý, đây là câu trả lời tốt nhất. Sạch sẽ, đơn giản và KHÔ (Đừng lặp lại chính mình).
Nolonar

Vấn đề với DataContractSerializer là nó tuần tự hóa và giải tuần tự hóa theo thứ tự bảng chữ cái, vì vậy nếu bạn cố gắng giải tuần tự hóa một thứ gì đó theo thứ tự sai, nó sẽ thất bại một cách âm thầm với các thuộc tính null trong đối tượng của bạn
superjugy

14

Tạo một thay thế nối tiếp.

Ví dụ, bạn có một lớp với thuộc tính công cộng thuộc loại Từ điển.

Để hỗ trợ tuần tự hóa Xml loại này, hãy tạo một lớp khóa-giá trị chung:

public class SerializeableKeyValue<T1,T2>
{
    public T1 Key { get; set; }
    public T2 Value { get; set; }
}

Thêm một thuộc tính XmlIgnore vào thuộc tính ban đầu của bạn:

    [XmlIgnore]
    public Dictionary<int, string> SearchCategories { get; set; }

Đặt ra một thuộc tính công khai của kiểu mảng, chứa một mảng các thể hiện serializableKeyValue, được sử dụng để tuần tự hóa và giải tuần tự hóa vào thuộc tính SearchC loại:

    public SerializeableKeyValue<int, string>[] SearchCategoriesSerializable
    {
        get
        {
            var list = new List<SerializeableKeyValue<int, string>>();
            if (SearchCategories != null)
            {
                list.AddRange(SearchCategories.Keys.Select(key => new SerializeableKeyValue<int, string>() {Key = key, Value = SearchCategories[key]}));
            }
            return list.ToArray();
        }
        set
        {
            SearchCategories = new Dictionary<int, string>();
            foreach (var item in value)
            {
                SearchCategories.Add( item.Key, item.Value );
            }
        }
    }

Tôi thích điều này bởi vì nó tách rời serialization từ thành viên từ điển. Nếu tôi có một lớp được sử dụng nhiều mà tôi muốn thêm các khả năng tuần tự hóa, thì việc gói từ điển có thể gây ra sự gián đoạn với các kiểu kế thừa.
VoteCoffee

Một lời cảnh báo cho bất kỳ ai thực hiện điều này: nếu bạn cố gắng biến tài sản thay thế của mình thành Danh sách (hoặc bất kỳ bộ sưu tập nào khác ), trình tuần tự XML sẽ không gọi trình setter (thay vào đó, nó gọi hàm getter và cố gắng thêm vào danh sách được trả về, mà rõ ràng không phải là những gì bạn muốn). Dán vào các mảng cho mẫu này.
Fraxtil

9

Bạn nên khám phá Json.Net, khá dễ sử dụng và cho phép các đối tượng Json được giải tuần tự trực tiếp trong Từ điển.

james_newtonking

thí dụ:

string json = @"{""key1"":""value1"",""key2"":""value2""}";
Dictionary<string, string> values = JsonConvert.DeserializeObject<Dictionary<string, string>>(json); 
Console.WriteLine(values.Count);
// 2
Console.WriteLine(values["key1"]);
// value1

6

Từ điển và Hashtables không được tuần tự hóa với XmlSerializer. Do đó bạn không thể sử dụng chúng trực tiếp. Một cách giải quyết khác là sử dụng XmlIgnorethuộc tính để ẩn các thuộc tính đó khỏi bộ nối tiếp và hiển thị chúng thông qua danh sách các cặp khóa-giá trị tuần tự hóa.

PS: xây dựng một cái XmlSerializerrất tốn kém, vì vậy hãy luôn lưu trữ nó nếu có cơ hội có thể sử dụng lại nó.


4

Tôi muốn có một lớp serializableDixi sử dụng các thuộc tính xml cho khóa / giá trị để tôi điều chỉnh lớp của Paul Welter.

Điều này tạo ra xml như:

<Dictionary>
  <Item Key="Grass" Value="Green" />
  <Item Key="Snow" Value="White" />
  <Item Key="Sky" Value="Blue" />
</Dictionary>"

Mã số:

using System.Collections.Generic;
using System.Xml;
using System.Xml.Linq;
using System.Xml.Serialization;

namespace DataTypes {
    [XmlRoot("Dictionary")]
    public class SerializableDictionary<TKey, TValue>
        : Dictionary<TKey, TValue>, IXmlSerializable {
        #region IXmlSerializable Members
        public System.Xml.Schema.XmlSchema GetSchema() {
            return null;
        }

        public void ReadXml(XmlReader reader) {
            XDocument doc = null;
            using (XmlReader subtreeReader = reader.ReadSubtree()) {
                doc = XDocument.Load(subtreeReader);
            }
            XmlSerializer serializer = new XmlSerializer(typeof(SerializableKeyValuePair<TKey, TValue>));
            foreach (XElement item in doc.Descendants(XName.Get("Item"))) {
                using(XmlReader itemReader =  item.CreateReader()) {
                    var kvp = serializer.Deserialize(itemReader) as SerializableKeyValuePair<TKey, TValue>;
                    this.Add(kvp.Key, kvp.Value);
                }
            }
            reader.ReadEndElement();
        }

        public void WriteXml(System.Xml.XmlWriter writer) {
            XmlSerializer serializer = new XmlSerializer(typeof(SerializableKeyValuePair<TKey, TValue>));
            XmlSerializerNamespaces ns = new XmlSerializerNamespaces();
            ns.Add("", "");
            foreach (TKey key in this.Keys) {
                TValue value = this[key];
                var kvp = new SerializableKeyValuePair<TKey, TValue>(key, value);
                serializer.Serialize(writer, kvp, ns);
            }
        }
        #endregion

        [XmlRoot("Item")]
        public class SerializableKeyValuePair<TKey, TValue> {
            [XmlAttribute("Key")]
            public TKey Key;

            [XmlAttribute("Value")]
            public TValue Value;

            /// <summary>
            /// Default constructor
            /// </summary>
            public SerializableKeyValuePair() { }
        public SerializableKeyValuePair (TKey key, TValue value) {
            Key = key;
            Value = value;
        }
    }
}
}

Bài kiểm tra đơn vị:

using System.IO;
using System.Linq;
using System.Xml;
using System.Xml.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace DataTypes {
    [TestClass]
    public class SerializableDictionaryTests {
        [TestMethod]
        public void TestStringStringDict() {
            var dict = new SerializableDictionary<string, string>();
            dict.Add("Grass", "Green");
            dict.Add("Snow", "White");
            dict.Add("Sky", "Blue");
            dict.Add("Tomato", "Red");
            dict.Add("Coal", "Black");
            dict.Add("Mud", "Brown");

            var serializer = new System.Xml.Serialization.XmlSerializer(dict.GetType());
            using (var stream = new MemoryStream()) {
                // Load memory stream with this objects xml representation
                XmlWriter xmlWriter = null;
                try {
                    xmlWriter = XmlWriter.Create(stream);
                    serializer.Serialize(xmlWriter, dict);
                } finally {
                    xmlWriter.Close();
                }

                // Rewind
                stream.Seek(0, SeekOrigin.Begin);

                XDocument doc = XDocument.Load(stream);
                Assert.AreEqual("Dictionary", doc.Root.Name);
                Assert.AreEqual(dict.Count, doc.Root.Descendants().Count());

                // Rewind
                stream.Seek(0, SeekOrigin.Begin);
                var outDict = serializer.Deserialize(stream) as SerializableDictionary<string, string>;
                Assert.AreEqual(dict["Grass"], outDict["Grass"]);
                Assert.AreEqual(dict["Snow"], outDict["Snow"]);
                Assert.AreEqual(dict["Sky"], outDict["Sky"]);
            }
        }

        [TestMethod]
        public void TestIntIntDict() {
            var dict = new SerializableDictionary<int, int>();
            dict.Add(4, 7);
            dict.Add(5, 9);
            dict.Add(7, 8);

            var serializer = new System.Xml.Serialization.XmlSerializer(dict.GetType());
            using (var stream = new MemoryStream()) {
                // Load memory stream with this objects xml representation
                XmlWriter xmlWriter = null;
                try {
                    xmlWriter = XmlWriter.Create(stream);
                    serializer.Serialize(xmlWriter, dict);
                } finally {
                    xmlWriter.Close();
                }

                // Rewind
                stream.Seek(0, SeekOrigin.Begin);

                XDocument doc = XDocument.Load(stream);
                Assert.AreEqual("Dictionary", doc.Root.Name);
                Assert.AreEqual(3, doc.Root.Descendants().Count());

                // Rewind
                stream.Seek(0, SeekOrigin.Begin);
                var outDict = serializer.Deserialize(stream) as SerializableDictionary<int, int>;
                Assert.AreEqual(dict[4], outDict[4]);
                Assert.AreEqual(dict[5], outDict[5]);
                Assert.AreEqual(dict[7], outDict[7]);
            }
        }
    }
}

1
Có vẻ tốt nhưng nó thất bại với một từ điển trống. Bạn cần kiểm tra reader.IsEmptyEuity trong phương thức ReadXML.
AnthonyVO

2

lớp Dictionary thực hiện ISerializable. Định nghĩa của Từ điển lớp được đưa ra dưới đây.

[DebuggerTypeProxy(typeof(Mscorlib_DictionaryDebugView<,>))]
[DebuggerDisplay("Count = {Count}")]
[Serializable]
[System.Runtime.InteropServices.ComVisible(false)]
public class Dictionary<TKey,TValue>: IDictionary<TKey,TValue>, IDictionary, IReadOnlyDictionary<TKey, TValue>, ISerializable, IDeserializationCallback  

Tôi không nghĩ đó là vấn đề. tham khảo liên kết dưới đây, trong đó nói rằng nếu bạn đang có bất kỳ loại dữ liệu nào khác không được tuần tự hóa thì Từ điển sẽ không được tuần tự hóa. http://forums.asp.net/t/1734187.aspx?Is+Dixi+serializable+


Điều đó đúng trong các phiên bản mới nhất, nhưng trong .NET 2, Dictionary không được tuần tự hóa, ngay cả ngày nay. Tôi đã xác nhận nó ngay hôm nay với một dự án nhắm mục tiêu .NET 3.5, đó là cách tôi tìm thấy chủ đề này.
Bruce

2

Bạn có thể sử dụng ExtendedXmlSerializer . Nếu bạn có một lớp học:

public class ConfigFile
{
    public String guiPath { get; set; }
    public string configPath { get; set; }
    public Dictionary<string, string> mappedDrives {get;set;} 

    public ConfigFile()
    {
        mappedDrives = new Dictionary<string, string>();
    }
}

và tạo thể hiện của lớp này:

ConfigFile config = new ConfigFile();
config.guiPath = "guiPath";
config.configPath = "configPath";
config.mappedDrives.Add("Mouse", "Logitech MX Master");
config.mappedDrives.Add("keyboard", "Microsoft Natural Ergonomic Keyboard 4000");

Bạn có thể tuần tự hóa đối tượng này bằng ExtendedXmlSerializer:

ExtendedXmlSerializer serializer = new ExtendedXmlSerializer();
var xml = serializer.Serialize(config);

Xml đầu ra sẽ như sau:

<?xml version="1.0" encoding="utf-8"?>
<ConfigFile type="Program+ConfigFile">
    <guiPath>guiPath</guiPath>
    <configPath>configPath</configPath>
    <mappedDrives>
        <Item>
            <Key>Mouse</Key>
            <Value>Logitech MX Master</Value>
        </Item>
        <Item>
            <Key>keyboard</Key>
            <Value>Microsoft Natural Ergonomic Keyboard 4000</Value>
        </Item>
    </mappedDrives>
</ConfigFile>

Bạn có thể cài đặt ExtendedXmlSerializer từ nuget hoặc chạy lệnh sau:

Install-Package ExtendedXmlSerializer

Đây là ví dụ trực tuyến


Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.