序列化字典包装器时出现问题

时间:2011-02-25 12:39:27

标签: c# .net xml serialization

我定义了两个类。第一个......

[Serializable]
public class LocalizationEntry
{
    public LocalizationEntry()
    {
        this.CatalogName = string.Empty;
        this.Identifier = string.Empty;
        this.Translation = new Dictionary<string, string>();
        this.TranslationsList = new List<Translation>();
    }

    public string CatalogName
    {
        get;
        set;
    }

    public string Identifier
    {
        get;
        set;
    }

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

    [XmlArray(ElementName = "Translations")]
    public List<Translation> TranslationsList
    {
        get
        {
            var list = new List<Translation>();

            foreach (var item in this.Translation)
            {
                list.Add(new Translation(item.Key, item.Value));
            }

            return list;
        }
        set
        {
            foreach (var item in value)
            {
                this.Translation.Add(item.Language, item.Text);
            }
        }
    }
}

...其中public List<Translation> TranslationsList是非序列化public Dictionary<string, string> Translation的包装器。

键和值的对定义如下:

[Serializable]
public class Translation
{
    [XmlAttribute(AttributeName = "lang")]
    public string Language
    {
        get;
        set;
    }

    [XmlText]
    public string Text
    {
        get;
        set;
    }

    public Translation()
    {

    }

    public Translation(string language, string translation)
    {
        this.Language = language;
        this.Text = translation;
    }
}

最后用于序列化的代码:

static void Main(string[] args)
{
    LocalizationEntry entry = new LocalizationEntry()
    {
        CatalogName = "Catalog",
        Identifier = "Id",
    };

    entry.Translation.Add("PL", "jabłko");
    entry.Translation.Add("EN", "apple");
    entry.Translation.Add("DE", "apfel");

    using (FileStream stream = File.Open(@"C:\entry.xml", FileMode.Create))
    {
        XmlSerializer serializer = new XmlSerializer(typeof(LocalizationEntry));
        serializer.Serialize(stream, entry);
    }

    LocalizationEntry deserializedEntry;
    using (FileStream stream = File.Open(@"C:\entry.xml", FileMode.Open))
    {
        XmlSerializer serializer = new XmlSerializer(typeof(LocalizationEntry));
        deserializedEntry = (LocalizationEntry)serializer.Deserialize(stream);
    }
}

问题是反序列化后deserializedEntry.TranslationsList为空。我在LocalizationEntry.TransalionsList的setter设置了一个断点,它也来自反序列化器。序列化产品当然是有效的。我的代码有差距吗?

编辑:

这是生成的XML:

<?xml version="1.0"?>
<LocalizationEntry xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <CatalogName>Catalog</CatalogName>
  <Identifier>Id</Identifier>
  <Translations>
    <Translation lang="PL">jabłko</Translation>
    <Translation lang="EN">apple</Translation>
    <Translation lang="DE">apfel</Translation>
  </Translations>
</LocalizationEntry>

3 个答案:

答案 0 :(得分:2)

问题是您的TranslationList属性未由Xml反序列化程序设置。 set方法将被命中,但只能通过调用this.TranslationsList = new List();在LocalisationEntry构造函数中。我还不确定为什么,但我怀疑它是因为它不知道如何将一组Translation对象转换回List。

我添加了以下代码,它运行良好:

[XmlArray(ElementName = "Translations")]
public Translation[] TranslationArray
{
    get
    {
        return TranslationsList.ToArray();
    }

    set
    {
        TranslationsList = new List<Translation>(value);
    }
}

[XmlIgnore]
public List<Translation> TranslationsList
....

答案 1 :(得分:1)

我猜这个问题与此有关:

public List<Translation> TranslationsList

get / set运算符仅用于获取或分配完整形式的列表。例如,如果您尝试在自己的代码中使用它,那么每次执行类似

的操作时

TranslationsList.Add(item)

它只会从现有字典中创建一个新列表,而不会实际处理您的项目。我打赌反序列化器的工作原理大致相同:使用set创建一次新对象,然后使用get,因为它添加了XML中的每个项目。因为get中发生的所有事情都是字典中的副本(当你开始反序列化时它是空的)你最终什么都没有。

尝试用一个字段替换它:

public List<Translation> TranslationsList;

然后在序列化之前显式调用代码将字典复制到此列表,并在反序列化后将其从此列表复制到字典中。假设有效,你可以找到一种更无缝的方式来实现你想要做的事情。

答案 2 :(得分:0)

我已经创建了一个示例,它可以让您在使用XmlSerializer时避免不必要的隐藏属性:

class Program
{
    static void Main(string[] args)
    {
        LocalizationEntry entry = new LocalizationEntry()
        {
            CatalogName = "Catalog",
            Identifier = "Id",
            Translations =
            {
                { "PL", "jabłko" },
                { "EN", "apple" },
                { "DE", "apfel" }
            }
        };

        using (MemoryStream stream = new MemoryStream())
        {
            XmlSerializer serializer = new XmlSerializer(typeof(LocalizationEntry));
            serializer.Serialize(stream, entry);

            stream.Seek(0, SeekOrigin.Begin);
            LocalizationEntry deserializedEntry = (LocalizationEntry)serializer.Deserialize(stream);
            serializer.Serialize(Console.Out, deserializedEntry);
        }
    }
}

public class LocalizationEntry
{
    public LocalizationEntry() { this.Translations = new TranslationCollection(); }
    public string CatalogName { get; set; }
    public string Identifier { get; set; }

    [XmlArrayItem]
    public TranslationCollection Translations { get; private set; }
}

public class TranslationCollection
    : Collection<Translation>
{
    public TranslationCollection(params Translation[] items)
    {
        if (null != items)
        {
            foreach (Translation item in items)
            {
                this.Add(item);
            }
        }
    }

    public void Add(string language, string text)
    {
        this.Add(new Translation
        {
            Language = language,
            Text = text
        });
    }
}

public class Translation
{
    [XmlAttribute(AttributeName = "lang")]
    public string Language { get; set; }

    [XmlText]
    public string Text { get; set; }
}

使用XmlSerializer类本身时存在一些缺点。 .NET指南鼓励您不要为集合属性(如翻译列表)提供公共设置器。但是当你查看XmlSerializer生成的代码时,你会看到它将使用Setter而不管它是否可访问。当临时类由XmlSerializer动态加载时,这会导致编译错误。避免这种情况的唯一方法是让XmlSerializer认为它实际上不能创建列表的实例,因此不会尝试为它调用set。如果XmlSerializer检测到它无法创建实例,它将抛出异常而不是使用Setter,并且成功编译临时类。我使用了param-keyword来欺骗序列化程序,认为没有默认构造函数。

此解决方案的唯一缺点是您必须在我的示例中为属性(TranslationCollection)使用非泛型的非接口类型。