1

次のコードがあります。

    [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 の本体をコメント アウトすると、上記と同じもの (既定値のもの) が得られます。パブリック メソッド、ヘルパー クラスのすべてで試しましたが、機能しません。つまり、期待される代わりに:

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

私は手に入れます

<CustomClass />

このクラスでリフレクションを使用すると、シリアライズができないようです。または要約すると: Init を呼び出すとき、またはプロパティをリフレクションで埋めるとき -> シリアル化が失敗し、このコード部分を削除すると -> シリアル化は機能しますが、もちろん値はありません。

これは本当ですか?そして、誰かが私の解決策の代替案を知っていますか?

説明に基づいてデータベースから自動的に何かを取得する必要があり、これが何も返さない場合は、DefaultValue にフォールバックします...

PS1: XmlSerializer を使用しています

PS2: シリアル化の前にブレークポイントを設定すると、すべてのプロパティが適切な値 (71、72 など) で満たされていることがわかります。

4

1 に答える 1

1

奇妙なことに、このクラスをシリアライズしようとすると、空のノード CustomClass が取得されます。

XmlSerializerを使用DefaultValueして、シリアル化する値を決定します。デフォルト値と一致する場合は、保存しません。このアプローチは、データ バインディングやモデル バインディングなどの同様のモデルと一致しています。

率直に言って、この場合はDefaultValueAttributeとの両方DescriptionAttributeが悪い選択だと思います。あなた自身を書いてください - おそらくEavInitAttribute- そして次のようなものを使用してください:

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

この条件付きシリアル化を制御する方法は他にもあることに注意してください。次のようなメソッドを記述できます。

public bool ShouldSerializeName() { return true; }

これは、値を書き込むように説得するために機能します (これは、さまざまなシリアライゼーションおよびデータバインディング API によって認識される別のパターンです) public。 APIの混乱)。

最後に、新しいオブジェクトを構築するたびにデータベースに複数回 (プロパティごとに 1 回) ヒットすることは非常にコストがかかると言えます。特に、これらの値の多くはいずれにしてもすぐに値が割り当てられる可能性が高いためです(したがって、それらを調べるのは無駄な労力です)。 . 私なら、これを「怠惰」と「キャッシュ」の両方にするために多くのことを考えます。


怠惰で「まばらな」実装の例:

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 ?? "";
        }
    }
}
于 2013-08-23T09:01:16.127 に答える