【问题标题】:Serialize a class that uses reflection to fill its properties序列化使用反射填充其属性的类
【发布时间】:2013-08-23 08:10:51
【问题描述】:

我有以下代码:

    [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 或用反射填充我的属性时 -> 序列化失败,当我删除此代码部分时 -> 序列化工作但当然没有我的值。

这是真的吗?有人知道我的解决方案的替代方案吗?

它应该根据描述自动从数据库中获取一些东西,当它什么都不返回时它会回退到 DefaultValue...

PS1:我正在使用 XmlSerializer

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

【问题讨论】:

  • 您使用的是什么序列化程序(这很重要)- 是 xml 吗?什么问题正在显现?症状是什么?我们如何区分这里的“工作”和“不工作”?即“请给我们更多信息……”?
  • 顺便说一句,[Serializable] 建议 BinaryFormatter,但“我将获得完整的 xml 表示”建议 XmlSerializer - 因此我感到困惑;它们在构造函数周围有非常不同的规则,因此了解哪些规则很重要;但老实说,在这里真正有帮助的是 5 行(或其他)如果它不能按预期工作,理想情况下以“我希望这个属性是 12 岁,因为(某些原因),但它会以 83 岁的身份回归

标签: c# serialization reflection


【解决方案1】:

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

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

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

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

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

public bool ShouldSerializeName() { return true; }

可以说服它写入值(这是另一种被各种序列化和数据绑定 API 识别的模式) - 但坦率地说,这是更多的工作(它是每个属性, 并且必须是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 ?? "";
        }
    }
}

【讨论】:

  • 谢谢,所以很可能是 DefaultValueAttribute 造成了这种行为,我会调查一下,谢谢!
  • @GerjanOnline 确定它负责
  • 是的,很抱歉,我现在无法测试它,但现在很明显这是问题所在。再次感谢关于您的最后一点:我将此类用作一种“设置类”,它将以 XML 格式输出其设置,以便可以覆盖设置。这意味着我只会给这堂课打电话 1 次。但是你是对的,构造函数是一个不好的地方,我会做一个静态帮助方法来做到这一点并在正确的地方调用它。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-02-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-12-05
  • 1970-01-01
  • 2023-04-07
相关资源
最近更新 更多