ทำให้เป็นอันดับคลาสที่ประกอบด้วยสมาชิกพจนานุกรม


144

เมื่อขยายจากปัญหาก่อนหน้าของฉันฉันได้ตัดสินใจที่จะ (de) จัดลำดับคลาสไฟล์ปรับแต่งของฉันซึ่งใช้งานได้ดี

ตอนนี้ฉันต้องการที่จะเก็บอาเรย์ของตัวอักษรไดรฟ์เพื่อ map (ที่สำคัญคือไดรฟ์ค่าเป็นเส้นทางเครือข่าย) และได้พยายามใช้Dictionary, HybridDictionaryและHashtableสำหรับเรื่องนี้ แต่ผมเคยได้รับข้อผิดพลาดต่อไปนี้เมื่อเรียกConfigFile.Load()หรือConfigFile.Save():

เกิดข้อผิดพลาดในการระบุประเภท 'App.ConfigFile' [snip] System.NotSupportedException: ไม่สามารถทำให้อนุกรมสมาชิก App.Configfile.mappedDrives [snip]

จากสิ่งที่ฉันอ่านพจนานุกรมและ HashTables สามารถทำให้เป็นอนุกรมได้ฉันจะทำอย่างไรผิด

[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();
            }
        }
    }
}

คำตอบ:


77

คุณไม่สามารถจัดลำดับคลาสที่ใช้ IDictionary ได้ ลองลิงค์นี้

ถาม: เหตุใดฉันจึงไม่สามารถซีเรียลแฮชของพอร์ตอนุกรมได้

ตอบ: XmlSerializer ไม่สามารถประมวลผลคลาสที่ใช้อินเทอร์เฟซ IDictionary ได้ นี่เป็นส่วนหนึ่งเนื่องจากข้อ จำกัด ด้านตารางเวลาและอีกส่วนหนึ่งเนื่องมาจากข้อเท็จจริงที่ว่า hashtable ไม่มีส่วนที่เหมือนกันในระบบประเภท XSD ทางออกเดียวคือการใช้ hashtable ที่กำหนดเองที่ไม่ได้ใช้อินเตอร์เฟซ IDictionary

ดังนั้นฉันคิดว่าคุณต้องสร้างพจนานุกรมเวอร์ชันของคุณเองสำหรับสิ่งนี้ ตรวจสอบคำถามอื่นนี้


4
เพียงแค่สงสัยว่าDataContractSerializerชั้นเรียนสามารถทำเช่นนั้นได้ เพียงแค่เอาต์พุตก็น่าเกลียดนิดหน่อย
rekire

187

มีวิธีการแก้ปัญหาที่เว็บบล็อกของ Paul Welter - พจนานุกรม Generic XML Serializable

ด้วยเหตุผลบางอย่างพจนานุกรมทั่วไปใน. net 2.0 ไม่ใช่ XML ที่สามารถทำให้เป็นอนุกรมได้ ข้อมูลโค้ดต่อไปนี้เป็นพจนานุกรมทั่วไปที่ทำให้เป็นอนุกรม xml พจนานุกรมนี้สามารถทำให้เป็นอนุกรมได้โดยใช้ส่วนต่อประสาน 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
}

17
+1 เฮ้เหตุใด Stack Overflow จึงไม่มีปุ่มคัดลอกรหัส อืมม? เพราะรหัสนี้มีค่าคัดลอก!
toddmo

1
+1 คำตอบที่ยอดเยี่ยม ใช้งานได้กับ SortedList เพียงเปลี่ยน "SerializableDictionary" เป็น "SerializableSortedList" และ "Dictionary <TKey, TValue>" เป็น "SortedList <TKey, TValue>"
kdmurray

1
+1 และหนึ่งข้อเสนอแนะ เมื่อวัตถุ SerializableDictionary ถือองค์ประกอบมากกว่าหนึ่งข้อยกเว้นจะถูกโยน ... ReadXml () และ WriteXml () ควรได้รับการแก้ไขเพื่อย้าย ReadStartElement ("รายการ"); และ WriteStartElement ("รายการ"); และ ReadEndElement () ที่เกี่ยวข้องและ WriteEndElement () จากลูป while
MNS

1
นั่นหมายความว่าในเฟรมเวิร์กในภายหลัง IDictionary นั้นเป็นอนุกรมหรือไม่?
โทมัส

1
การใช้งานนี้จะใช้งานได้หากพจนานุกรมกำลังจัดเก็บพูดstringค่า แต่จะโยนInvalidOperationExceptionเมื่อ deserialization กล่าวถึงองค์ประกอบ wrapper ที่ไม่คาดคิดถ้าคุณพยายามจัดเก็บวัตถุที่กำหนดเองหรืออาร์เรย์สตริงในนั้น (ดูคำถามของฉันสำหรับตัวอย่างของปัญหาที่ฉันเผชิญกับเรื่องนี้.)
Christopher Kyle Horton

57

แทนการใช้คุณสามารถใช้XmlSerializer System.Runtime.Serialization.DataContractSerializerสิ่งนี้สามารถเรียงลำดับพจนานุกรมและส่วนต่อประสานที่ไม่มีเหงื่อ

นี่คือลิงค์ไปยังตัวอย่างเต็มรูปแบบhttp://theburningmonk.com/2010/05/net-tips-xml-serialize-or-deserialize-dictionary-in-csharp/


2
คำตอบที่ดีที่สุด
DWRoelands

เห็นด้วยนี่เป็นคำตอบที่ดีที่สุด ทำความสะอาดง่ายและแห้ง (อย่าทำซ้ำตัวเอง)
โนโลนาร์

ปัญหากับ DataContractSerializer คือมันเป็นอนุกรมและดีซีเรียลไลซ์ตามลำดับตัวอักษรดังนั้นถ้าคุณพยายามที่จะ
ทำการดีซีเรียลไลซ์

14

สร้างตัวแทนเป็นอนุกรม

ตัวอย่างคุณมีคลาสที่มีคุณสมบัติพับลิกของชนิดพจนานุกรม

เพื่อรองรับ Xml การทำให้เป็นอนุกรมประเภทนี้ให้สร้างคลาสคีย์ - ค่าทั่วไป:

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

เพิ่มคุณสมบัติ XmlIgnore ให้กับคุณสมบัติดั้งเดิมของคุณ:

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

เปิดเผยคุณสมบัติพับลิกของชนิดอาร์เรย์ที่เก็บอาร์เรย์ของอินสแตนซ์ SerializableKeyValue ซึ่งใช้เพื่อซีเรียลไลซ์และดีซีเรียลไลซ์ลงในคุณสมบัติ SearchCategories:

    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 );
            }
        }
    }

ฉันชอบสิ่งนี้เพราะมันจะทำการแยกอนุกรมจากสมาชิกพจนานุกรม หากฉันมีคลาสที่ใช้งานหนักซึ่งฉันต้องการเพิ่มความสามารถในการซีเรียลไลซ์เซชั่นให้ดังนั้นการตัดพจนานุกรมอาจทำให้ตัวแบ่งประเภทที่สืบทอดมาได้
VoteCoffee

คำเตือนสำหรับทุกคนที่ใช้สิ่งนี้: หากคุณพยายามทำให้ตัวแทนตัวแทนของคุณเป็นรายการ (หรือคอลเลกชันอื่น ๆ) XML serializer จะไม่เรียก setter (แทนที่จะเรียกใช้getter แทนและพยายามเพิ่มลงในรายการที่ส่งคืน ซึ่งเห็นได้ชัดว่าไม่ใช่สิ่งที่คุณต้องการ) ติดกับอาร์เรย์สำหรับรูปแบบนี้
Fraxtil

9

คุณควรสำรวจ Json.Net ซึ่งค่อนข้างใช้งานง่ายและอนุญาตให้แยกวัตถุ Json ในพจนานุกรมโดยตรง

james_newtonking

ตัวอย่าง:

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

พจนานุกรมและ Hashtables ไม่ได้ serializable XmlSerializerกับ ดังนั้นคุณไม่สามารถใช้พวกเขาโดยตรง วิธีแก้ปัญหาคือใช้XmlIgnoreททริบิวต์เพื่อซ่อนคุณสมบัติเหล่านั้นจาก serializer และเปิดเผยผ่านรายการคู่ค่าคีย์ - ค่า

ป.ล. : การสร้างเครื่องXmlSerializerมีราคาแพงมากดังนั้นให้ทำการแคชไว้เสมอหากมีโอกาสที่จะใช้งานได้อีกครั้ง


4

ฉันต้องการคลาส SerializableDictionary ที่ใช้แอตทริบิวต์ xml สำหรับคีย์ / ค่าดังนั้นฉันจึงปรับคลาสของ Paul Welter

สิ่งนี้สร้าง xml เช่น:

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

รหัส:

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;
        }
    }
}
}

ทดสอบหน่วย:

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
ดูดี แต่ล้มเหลวด้วยพจนานุกรมว่างเปล่า คุณต้องการผู้อ่านการทดสอบ IsEmptyElement ในวิธี ReadXML
AnthonyVO

2

คลาส Dictionary ใช้ ISerializable คำจำกัดความของ Class Dictionary ที่ระบุด้านล่าง

[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  

ฉันไม่คิดว่าเป็นปัญหา อ้างถึงลิงค์ด้านล่างซึ่งระบุว่าหากคุณมีประเภทข้อมูลอื่นใดที่ไม่ได้เป็นแบบอนุกรมพจนานุกรมจะไม่ถูกทำให้เป็นอนุกรม http://forums.asp.net/t/1734187.aspx?Is+Dictionary+serializable+


นั่นเป็นความจริงในรุ่นล่าสุด แต่ใน. NET 2 พจนานุกรมยังไม่สามารถต่อเนื่องได้แม้กระทั่งทุกวันนี้ ฉันยืนยันเพียงแค่วันนี้ด้วยโครงการที่มีเป้าหมายเป็น. NET 3.5 ซึ่งเป็นวิธีที่ฉันค้นพบเธรดนี้
บรูซ

2

คุณสามารถใช้ExtendedXmlSerializer หากคุณมีชั้นเรียน:

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

และสร้างอินสแตนซ์ของคลาสนี้:

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");

คุณสามารถทำให้เป็นอันดับวัตถุนี้โดยใช้ ExtendedXmlSerializer:

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

เอาต์พุต xml จะมีลักษณะดังนี้:

<?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>

คุณสามารถติดตั้ง ExtendedXmlSerializer จากnugetหรือรันคำสั่งต่อไปนี้:

Install-Package ExtendedXmlSerializer

นี่คือตัวอย่างออนไลน์


0

บทความนี้จะอธิบายวิธีจัดการกับสิ่งนี้อย่างแน่นอน: ฉันจะ ... เรียงลำดับตารางแฮชใน C # ได้อย่างไรเมื่อแอปพลิเคชันต้องการ

ฉันหวังว่านี้จะเป็นประโยชน์


คำตอบสำหรับลิงค์เท่านั้นอาจมีการเปลี่ยนแปลงเล็กน้อยและควรสรุปไว้ในเนื้อหาของคำตอบด้วยรายละเอียดที่สำคัญ
Christopher Kyle Horton
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.