사전 멤버를 포함하는 클래스 직렬화


144

이전 문제를 확장하면서 , 나는 잘 작동하는 구성 파일 클래스를 (직렬화)하기로 결정했습니다.

지금은 (값은 네트워크 경로 키가 드라이브 문자) 매핑 및 사용 시도 드라이브 문자 연관 배열을 저장하려는 Dictionary, HybridDictionary그리고 Hashtable이뿐만 호출 할 때 나는 항상 다음과 같은 오류가 ConfigFile.Load()ConfigFile.Save():

'App.ConfigFile'유형을 반영하는 중에 오류가 발생했습니다. [snip] System.NotSupportedException : 멤버 App.Configfile.mappedDrives를 serialize 할 수 없습니다. [snip]

내가 읽은 것에서 사전 및 해시 테이블을 직렬화 할 수 있으므로 내가 뭘 잘못하고 있습니까?

[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를 구현하는 클래스를 직렬화 할 수 없습니다. 이 링크를 확인하십시오 .

Q : 해시 테이블을 직렬화 할 수없는 이유는 무엇입니까?

A : XmlSerializer는 IDictionary 인터페이스를 구현하는 클래스를 처리 할 수 ​​없습니다. 이는 부분적으로 스케줄 제한 사항과 부분적으로 해시 테이블에 XSD 유형 시스템에 대응하는 것이 없기 때문입니다. 유일한 해결책은 IDictionary 인터페이스를 구현하지 않는 사용자 지정 해시 테이블을 구현하는 것입니다.

그래서 당신은 이것을 위해 자신의 사전 버전을 만들어야한다고 생각합니다. 이 다른 질문을 확인하십시오 .


4
DataContractSerializer수업이 궁금 할 뿐입니다 . 단지 출력이 약간 추합니다.
23:36에

186

Paul Welter의 Weblog-XML Serializable Generic Dictionary에 솔루션이 있습니다.

어떤 이유로 .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
}

16
+1. 왜 Stack Overflow에 복사 코드 버튼이 없습니까? 흠? 이 코드는 복사 할 가치가 있습니다.
toddmo

1
환상적인 답변 +1. SortedList에서도 작동합니다. "SerializableDictionary"를 "SerializableSortedList"로, "Dictionary <TKey, TValue>"를 "SortedList <TKey, TValue>"로 변경했습니다.
kdmurray

1
+1 및 하나의 제안. SerializableDictionary 객체에 둘 이상의 요소가 있으면 예외가 발생합니다. ReadXml () 및 WriteXml ()을 수정하여 ReadStartElement ( "item"); 및 WriteStartElement ( "item"); while 루프에서 연관된 ReadEndElement () 및 WriteEndElement ().
MNS

1
그렇다면 나중에 프레임 워크에서 IDictionary가 직렬화 가능하다는 의미입니까?
Thomas

1
이 구현은 사전이 string값을 저장하는 경우 작동 하지만 InvalidOperationException사용자 정의 객체 또는 문자열 배열을 저장하려고하면 예기치 않은 래퍼 요소를 언급하는 직렬화 해제를 발생시킵니다. ( 내가 직면 한 문제의 예는 내 질문 을 참조하십시오 .)
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 2016 년

동의합니다. 이것이 최선의 대답입니다. 깨끗하고 단순하며 건조합니다 (반복하지 마십시오).
Nolonar

DataContractSerializer의 문제점은 알파벳 순서로 직렬화 및 직렬화 해제한다는 것입니다. 따라서 잘못된 순서로 무언가를 직렬화 해제하려고하면 객체의 null 속성으로 인해 자동으로 실패합니다
superjugy

14

직렬화 대리를 만듭니다.

예를 들어, Dictionary 유형의 public 속성을 가진 클래스가 있습니다.

이 유형의 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; }

SearchCategories 속성을 직렬화하고 역 직렬화하는 데 사용되는 SerializableKeyValue 인스턴스의 배열을 보유하는 배열 유형의 공용 속성을 노출합니다.

    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

이것을 구현하는 모든 사람에게주의해야 할 말 : 대리 속성을 List (또는 다른 컬렉션 ) 로 만들려고 하면 XML serializer 는 setter를 호출하지 않고 getter를 호출하고 반환 된 목록에 추가하려고 시도합니다. 분명히 당신이 원하는 것이 아닙니다). 이 패턴의 배열을 고수하십시오.
Fraxtil

9

사용하기 쉽고 Json.Net을 Dictionary에서 직접 직렬화 해제 할 수있는 Json.Net을 탐색해야합니다.

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

사전 및 해시 테이블은로 직렬화 할 수 없습니다 XmlSerializer. 따라서 직접 사용할 수 없습니다. 해결 방법은 XmlIgnore속성 을 사용하여 해당 속성을 serializer에서 숨기고 serializable 키-값 쌍 목록을 통해 노출하는 것입니다.

추신 : 구성 XmlSerializer은 매우 비싸므로 재사용 할 수있는 경우 항상 캐시하십시오.


4

키 / 값에 xml 특성을 사용하는 SerializableDictionary 클래스를 원했기 때문에 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
좋아 보이지만 사전이 비어 있으면 실패합니다. ReadXML 메서드에서 reader.IsEmptyElement 테스트가 필요합니다.
AnthonyVO

2

Dictionary 클래스는 ISerializable을 구현합니다. 아래에 주어진 클래스 사전의 정의.

[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에서는 Dictionary조차도 오늘날에도 직렬화 할 수 없습니다. .NET 3.5를 대상으로하는 프로젝트로 오늘 확인했습니다.이 스레드를 찾는 방법입니다.
Bruce

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>

nuget 에서 ExtendedXmlSerializer를 설치 하거나 다음 명령을 실행할 수 있습니다 .

Install-Package ExtendedXmlSerializer

다음은 온라인 예


당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.