序列化一个使用反射来填充其属性的类

时间:2013-08-23 08:10:51

标签: c# serialization reflection

我有以下代码:

    [Serializable]
    public class CustomClass
    {
        public CustomClass()
        {
            this.Init();
        }

        public void Init()
        {
            foreach (PropertyInfo p in this.GetType().GetProperties())
            {
                DescriptionAttribute da = null;
                DefaultValueAttribute dv = null;
                foreach (Attribute attr in p.GetCustomAttributes(true))
                {
                    if (attr is DescriptionAttribute)
                    {
                        da = (DescriptionAttribute) attr;
                    }
                    if (attr is DefaultValueAttribute)
                    {
                        dv = (DefaultValueAttribute) attr;
                    }
                }

                UInt32 value = 0;
                if (da != null && !String.IsNullOrEmpty(da.Description))
                {
                    value = Factory.Instance.SelectByCode(da.Description, 3);
                }

                if (dv != null && value == 0)
                {
                    value = (UInt32) dv.Value;
                }

                p.SetValue(this, value, null);
            }
        }

        private UInt32 name;

        [Description("name")]
        [DefaultValue(41)]
        public UInt32 Name
        {
            get { return this.name; }
            set { this.name = value; }
        }

        (30 more properties)
    }

现在奇怪的是:当我尝试序列化这个类时,我会得到一个空节点CustomClass!

<CustomClass />

当我从构造函数中删除Init时,它按预期工作!我将得到类的完整xml表示但是没有值(所有值为0)。

<CustomClass>
    <Name>0</Name>
    ...
</CustomClass>

另外,当我注释掉Init的主体时,我将得到与上面相同的内容(具有默认值的那个) 我已经尝试了一个公共方法,Helper类的一切,但它不起作用。也就是说,而不是预期的:

<CustomClass>
    <Name>15</Name>
    ...
</CustomClass>

我会得到

<CustomClass />

当我在这个类中使用反射时,似乎无法进行序列化。 或者总结一下:当我调用Init或用反射填充我的属性时 - &gt;序列化失败,当我删除此代码部分 - &gt;序列化工作但当然没有我的价值观。

这是真的吗?有人知道我的解决方案的另一种选择吗?

它应该根据描述自动从数据库中获取内容,当它返回任何内容时,它会回退到DefaultValue ...

PS1:我正在使用XmlSerializer

PS2:当我在序列化之前设置断点时,我可以看到所有属性都填充了良好的值(如71,72等)。

1 个答案:

答案 0 :(得分:1)

  

现在奇怪的是:当我尝试序列化这个类时,我会得到一个空节点CustomClass!

XmlSerializer使用DefaultValue来决定序列化哪些值 - 如果它与默认值匹配,则不会存储。这种方法与数据绑定/模型绑定等类似模型一致。

坦率地说,我会说在这种情况下,DefaultValueAttributeDescriptionAttribute都是不好的选择。写你自己的 - 也许是EavInitAttribute - 然后使用类似的东西:

[EavInit(41, "name")]
public uint Name {get;set;}

请注意,有其他方法来控制此条件序列化 - 您可以编写如下方法:

public bool ShouldSerializeName() { return true; }

工作以说服它写入值(这是各种序列化和数据绑定API识别的另一种模式) - 但坦率地说这是更多的工作(它是per-property ,并且需要public,因此它会使API变得混乱。

最后,我会说每次新对象构建多次访问数据库(每个属性一次)是非常昂贵的 - 特别是因为很多这些值很可能会在中分配值 >(所以查找它们是浪费精力)。如果是我的话,我会花很多心思把这个“懒惰”和“缓存”。


懒惰和“稀疏”实现的一个例子:

using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Xml.Serialization;

static class Program
{
    static void Main()
    {
        var obj = new CustomClass();
        Console.WriteLine(obj.Name);

        // show it working via XmlSerializer
        new XmlSerializer(obj.GetType()).Serialize(Console.Out, obj);
    }
}
public class CustomClass : EavBase
{
    [EavInit(42, "name")]
    public uint Name
    {
        get { return GetEav(); }
        set { SetEav(value); }
    }
}
public abstract class EavBase
{
    private Dictionary<string, uint> values;
    protected uint GetEav([CallerMemberName] string propertyName = null)
    {
        if (values == null) values = new Dictionary<string, uint>();
        uint value;
        if (!values.TryGetValue(propertyName, out value))
        {
            value = 0;
            var prop = GetType().GetProperty(propertyName);
            if (prop != null)
            {
                var attrib = (EavInitAttribute)Attribute.GetCustomAttribute(
                    prop, typeof(EavInitAttribute));
                if (attrib != null)
                {
                    value = attrib.DefaultValue;
                    if (!string.IsNullOrEmpty(attrib.Key))
                    {
                        value = LookupDefaultValueFromDatabase(attrib.Key);
                    }
                }
            }
            values.Add(propertyName, value);
        }
        return value;
    }
    protected void SetEav(uint value, [CallerMemberName] string propertyName = null)
    {
        (values ?? (values = new Dictionary<string, uint>()))[propertyName] = value;
    }
    private static uint LookupDefaultValueFromDatabase(string key)
    {
        // TODO: real code here
        switch (key)
        {
            case "name":
                return 7;
            default:
                return 234;
        }
    }
    [AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
    protected class EavInitAttribute : Attribute
    {
        public uint DefaultValue { get; private set; }
        public string Key { get; private set; }
        public EavInitAttribute(uint defaultValue) : this(defaultValue, "") { }
        public EavInitAttribute(string key) : this(0, key) { }
        public EavInitAttribute(uint defaultValue, string key)
        {
            DefaultValue = defaultValue;
            Key = key ?? "";
        }
    }
}