zoukankan      html  css  js  c++  java
  • 动态类型序列化

    上一篇文章直接就被移除首页了,这次来点大家都能懂的干货.

    需求

    之前做一个winform的工具时候有以下几个需求
    1. 主窗体(或者叫平台)可以安装若干类型的插件。
    2. 插件关闭时候需要保存状态。
    3. 插件加载的时候可以加载上次关闭的配置。
    4. 插件中的配置可以切换。
    5. 主窗体本身保存当前插件,并且可以通过不同的配置文件切换插件

    使用上最方便的做法是将配置给平台来管理。但是平台本身并不知道插件要保存怎样的配置。针对以上问题在配置这个上做了如下设计

    设计

    1. 动态类型序列化以满足插件的任何配置需要
    2. 动态类型基本的就是dynamic,那么我们需用字典作为实现
    3. 支持具体的类进行序列化,那么此时需要用xml保存类的元数据信息
    4. 支持接口的序列化,此时也是保存实际类型的元数据信息
    5. 支持List序列化
    6. 支持Arry序列化
    7. 支持Dictionary序列化

    接口定义


    其中PathOrSourceString 属性这样既可以支持文件,也可以直接支持字符串,扩展更加方便.

    public interface IConfig
        {
            string PathOrSourceString { get; set; }
    
            dynamic Data { get; set; }
        }
    

    动态类型实现


    这里是基于字典,网上有很多类似的代码。

    这里字典的Value设计成dynamic是为了嵌套。

        [Serializable]
        public class DynamicDictionary : DynamicObject
        {
            private Dictionary<string, dynamic> _dictionary = new Dictionary<string, dynamic>();
    
            public override bool TryGetMember(GetMemberBinder binder, out object result)
            {
                string name = binder.Name;
                if (!_dictionary.ContainsKey(name))
                {
                    _dictionary.Add(name, new DynamicDictionary());
                }
                return _dictionary.TryGetValue(name, out result);
            }
    
            public override bool TrySetMember(SetMemberBinder binder, object value)
            {
                var key = binder.Name;
                if (_dictionary.ContainsKey(key))
                    _dictionary[key] = value;
                else
                {
                    _dictionary.Add(key, value);
                }
    
    
                return true;
            }
    
            public Dictionary<string, dynamic> Dictionary
            {
                get { return _dictionary; }
            }
    
            public void AddMember(string name, dynamic value)
            {
                _dictionary.Add(name, value);
            }
        }
    

      

    配置的加载和保存逻辑(核心)

        public static class ConfigManager
        {
            public static IConfig LoadFromFile(this IConfig config)
            {
                if (config == null || string.IsNullOrEmpty(config.PathOrSourceString))
                    throw new ArgumentNullException("config");
                if (!File.Exists(config.PathOrSourceString))
                {
                    return config;
                }
                var doc = new XmlDocument();
                doc.Load(config.PathOrSourceString);
                var element = doc["Data"];
                config.Data = GetValue(element);
                return config;
            }
    
            public static IConfig SaveToFile(this IConfig config)
            {
                if (config == null || string.IsNullOrEmpty(config.PathOrSourceString) || config.Data == null)
                    throw new ArgumentNullException("config");
                var dir = Path.GetDirectoryName(config.PathOrSourceString);
                if (!Directory.Exists(dir))
                    Directory.CreateDirectory(dir);
                var doc = new XmlDocument();
                doc.AppendChild(GetXml("Data", config.Data, doc));
                doc.Save(config.PathOrSourceString);
                return config;
            }
    
            public static IConfig LoadFromString(this IConfig config)
            {
                if (config == null || string.IsNullOrEmpty(config.PathOrSourceString))
                    throw new ArgumentNullException("config");
                var doc = new XmlDocument();
                doc.LoadXml(config.PathOrSourceString);
                var element = doc["Data"];
                config.Data = GetValue(element);
                return config;
            }
    
            public static IConfig SaveToString(this IConfig config)
            {
                if (config == null || config.Data == null)
                    throw new ArgumentNullException("config");
                var doc = new XmlDocument();
                doc.AppendChild(GetXml("Data", config.Data, doc));
                config.PathOrSourceString = doc.OuterXml;
                return config;
            }
    
            #region 解析XmlElement
    
            public static dynamic GetValue(XmlElement element)
            {
                if (element == null)
                    return null;
    
                Classify clasify;
                Enum.TryParse(element.GetAttribute("Classify"), out clasify);
                switch (clasify)
                {
                    case Classify.Sample:
                        return GetSampleValue(element.GetAttribute("Assembly"), element.GetAttribute("Type"), element.InnerText);
                    case Classify.Array:
                        return GetArrayValue(element.GetAttribute("ElementAssembly"), element.GetAttribute("ElementType"), element.GetChidlren());
                    case Classify.List:
                        return GetListValue(element.GetAttribute("GenericAssembly"), element.GetAttribute("GenericType"), element.GetChidlren());
                    case Classify.Dictionary:
                        return GetDictionaryValue(element.GetAttribute("KeyGenericAssembly"),
                            element.GetAttribute("KeyGenericType"),
                            element.GetAttribute("ValueGenericAssembly"),
                            element.GetAttribute("ValueGenericType"),
                            element.GetChidlren());
                    case Classify.Dynamic:
                        return GetDynamicValue(element.GetChidlren());
                    case Classify.Custom:
                        return GetCustomValue(element.GetAttribute("Assembly"), element.GetAttribute("Type"), element.GetChidlren());
                }
    
                return null;
            }
            public static object GetSampleValue(string assembly, string typeFullName, string value)
            {
                var type = Assembly.Load(assembly).GetType(typeFullName);
                if (type == null)
                    return null;
                return CoralConvert.Convert(value, type);
            }
            public static object GetListValue(string genericAssembly, string genericTypeName, List<XmlElement> elements)
            {
                var genericType = Assembly.Load(genericAssembly).GetType(genericTypeName);
                var type = typeof(List<>).MakeGenericType(genericType);
                dynamic list = Activator.CreateInstance(type, true);
    
                foreach (var element in elements)
                {
                    list.Add(GetValue(element));
                }
                return list;
            }
            public static object GetArrayValue(string elementAssembly, string elementTypeName, List<XmlElement> elements)
            {
                var elementType = Assembly.Load(elementAssembly).GetType(elementTypeName);
                dynamic list = Array.CreateInstance(elementType, elements.Count);
                for (int i = 0; i < elements.Count; i++)
                {
                    list[i] = GetValue(elements[i]);
                }
                return list;
            }
            public static object GetDictionaryValue(string keyAssembly, string keyTypeName, string valueAssembly, string valueTypeName, List<XmlElement> elements)
            {
                var keyType = Assembly.Load(keyAssembly).GetType(keyTypeName);
                var valueType = Assembly.Load(valueAssembly).GetType(valueTypeName);
                var type = typeof(Dictionary<,>).MakeGenericType(keyType, valueType);
                dynamic dict = Activator.CreateInstance(type, true);
                foreach (var element in elements)
                {
                    dict.Add(GetValue(element["Key"]), GetValue(element["Value"]));
                }
                return dict;
            }
            public static object GetDynamicValue(List<XmlElement> elements)
            {
                var dict = new DynamicDictionary();
                foreach (var element in elements)
                {
                    dict.Dictionary.Add(GetValue(element["Key"]), GetValue(element["Value"]));
                }
                return dict;
            }
            public static object GetCustomValue(string assemblyFullName, string typeFullName, List<XmlElement> elements)
            {
                var type = Assembly.Load(assemblyFullName).GetType(typeFullName);
                if (type == null)
                    return null;
                dynamic obj = Activator.CreateInstance(type, true);
    
                foreach (var element in elements)
                {
                    var property = type.GetProperty(element.Name);
                    object value;
                    if (!CoralConvert.Convert(GetValue(element), property.PropertyType, out value))
                        continue;
                    property.SetValue(obj, value);
                }
    
                return obj;
            }
            #endregion
    
            #region 创建XmlElement
    
            /// <summary>
            /// 创建xml元素
            /// </summary>
            /// <param name="name"></param>
            /// <param name="data"></param>
            /// <param name="doc"></param>
            /// <returns></returns>
            public static XmlElement GetXml(string name, object data, XmlDocument doc)
            {
                if (data == null)
                    return null;
                if (data.GetType().IsValueType || data is string)
                {
                    return GetValueTypeXml(name, data, doc);
                }
                var list = data as IList;
                if (list != null)
                {
                    return GetIListXml(name, list, doc);
                }
                var dict = data as IDictionary;
                if (dict != null)
                {
                    return GetIDictionaryXml(name, dict, doc);
                }
                var dynamic = data as DynamicDictionary;
                if (dynamic != null)
                {
                    return GetDynamicXml(name, dynamic, doc);
                }
                return GetCustomXml(name, data, doc);
            }
    
            /// <summary>
            /// 创建简单类型的xml元素
            /// </summary>
            /// <param name="name"></param>
            /// <param name="data"></param>
            /// <param name="doc"></param>
            /// <returns></returns>
            private static XmlElement GetValueTypeXml(string name, object data, XmlDocument doc)
            {
                if (data == null)
                    return null;
                var element = doc.CreateElement(name);
                element.SetAttribute("Type", data.GetType().FullName);
                element.SetAttribute("Assembly", MetaDataManager.Assembly.GetAssemblySortName(data.GetType().Assembly));
                element.SetAttribute("Classify", Classify.Sample.ToString());
                element.InnerText = data.ToString();
                return element;
            }
    
            /// <summary>
            /// 获取列表类型的xml
            /// </summary>
            /// <param name="name"></param>
            /// <param name="datas"></param>
            /// <param name="doc"></param>
            /// <returns></returns>
            private static XmlElement GetIListXml(string name, object datas, XmlDocument doc)
            {
                if (datas == null)
                    return null;
                var element = doc.CreateElement(name);
                if (datas.GetType().IsArray)
                {
                    element.SetAttribute("Type", typeof(Array).FullName);
                    element.SetAttribute("Classify", Classify.Array.ToString());
                    element.SetAttribute("ElementType", datas.GetType().GetElementType().FullName);
                    element.SetAttribute("ElementAssembly", datas.GetType().GetElementType().Assembly.FullName);
                }
                else
                {
                    element.SetAttribute("Type", typeof(IList).FullName);
                    element.SetAttribute("Classify", Classify.List.ToString());
                    element.SetAttribute("GenericType", datas.GetType().GenericTypeArguments[0].FullName);
                    element.SetAttribute("GenericAssembly", datas.GetType().GenericTypeArguments[0].Assembly.FullName);
                }
                foreach (var data in (IList)datas)
                {
                    element.AppendChild(GetXml("Element", data, doc));
                }
                return element;
            }
    
            /// <summary>
            /// 创建动态类型的xml
            /// </summary>
            /// <param name="name"></param>
            /// <param name="data"></param>
            /// <param name="doc"></param>
            /// <returns></returns>
            private static XmlElement GetDynamicXml(string name, dynamic data, XmlDocument doc)
            {
                if (data == null)
                    return null;
                var element = doc.CreateElement(name);
                element.SetAttribute("Type", "dynamic");
                element.SetAttribute("Classify", Classify.Dynamic.ToString());
                foreach (DictionaryEntry item in (IDictionary)data.Dictionary)
                {
                    var child = doc.CreateElement("Element");
                    child.AppendChild(GetXml("Key", item.Key ?? string.Empty, doc));
                    child.AppendChild(GetXml("Value", item.Value ?? string.Empty, doc));
                    element.AppendChild(child);
                }
                return element;
            }
    
            /// <summary>
            /// 创建字典类型的xml
            /// </summary>
            /// <param name="name"></param>
            /// <param name="datas"></param>
            /// <param name="doc"></param>
            /// <returns></returns>
            private static XmlElement GetIDictionaryXml(string name, object datas, XmlDocument doc)
            {
                if (datas == null)
                    return null;
                var element = doc.CreateElement(name);
                element.SetAttribute("Type", typeof(IDictionary).FullName);
                element.SetAttribute("Classify", Classify.Dictionary.ToString());
                element.SetAttribute("KeyGenericAssembly", datas.GetType().GetGenericArguments()[0].Assembly.FullName);
                element.SetAttribute("KeyGenericType", datas.GetType().GetGenericArguments()[0].FullName);
                element.SetAttribute("ValueGenericAssembly", datas.GetType().GetGenericArguments()[1].Assembly.FullName);
                element.SetAttribute("ValueGenericType", datas.GetType().GetGenericArguments()[1].FullName);
                foreach (DictionaryEntry data in (IDictionary)datas)
                {
                    var child = doc.CreateElement("Element");
                    child.AppendChild(GetXml("Key", data.Key ?? string.Empty, doc));
                    child.AppendChild(GetXml("Value", data.Value ?? string.Empty, doc));
                    element.AppendChild(child);
                }
                return element;
            }
    
            /// <summary>
            /// 创建自定义类
            /// </summary>
            /// <param name="name"></param>
            /// <param name="data"></param>
            /// <param name="doc"></param>
            /// <returns></returns>
            private static XmlElement GetCustomXml(string name, object data, XmlDocument doc)
            {
                if (data == null)
                    return null;
                var element = doc.CreateElement(name);
                element.SetAttribute("Assembly",MetaDataManager.Assembly.GetAssemblySortName(data.GetType().Assembly));
                element.SetAttribute("Type", data.GetType().FullName);
                element.SetAttribute("Classify", Classify.Custom.ToString());
                data.GetType().GetProperties().ForEach(property =>
                {
                    var item = GetXml(property.Name, property.GetValue(data), doc);
                    if (item != null)
                        element.AppendChild(item);
                });
                return element;
            }
    
            #endregion
    
            public enum Classify
            {
                Sample,
                List,
                Array,
                Dictionary,
                Dynamic,
                Custom,
            }
            public static List<XmlElement> GetChidlren(this XmlElement element)
            {
                return element.Cast<XmlElement>().ToList();
            }
        }
    

      

      

    核心思路就是递归,充分利用元数据

    测试代码

     public class XmlConfig : IConfig
        {
            public string PathOrSourceString { get; set; }
            public dynamic Data { get; set; }
        }
    
        public interface ITestModel
        {
    
            string Name { get; set; }
    
            string DataType { get; set; }
    
            string Data { get; set; }
    
    
        }
    
        public class TestConfig
        {
            public ITestModel Model { get; set; }
    
            public  List<ITestModel> List { get; set; }
            public Dictionary<string, ITestModel> Dict { get; set; }
        }
    
    
    
        public class TestModel: ITestModel
        {
            public string Name { get; set; }
    
            public string DataType { get; set; }
    
            public string Data { get; set; }
    
            public List<ITestModel> List { get; set; }
            public Dictionary<string, ITestModel> Dict { get; set; }
        }
    
    
        public class ConfigTest
        {
            public static void PerformanceTest()
            {
                var xmlconfig = new XmlConfig();
    
                xmlconfig.PathOrSourceString = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Configs", "test1.Config");
    
                #region list 类,字典测试
                
                var testModel=   new TestModel()
                {
                    Name = "1",
                    DataType = "1",
                    Data = "1",
                    List = new List<ITestModel>
                    {
                        new TestModel
                        {
                            Name = "2",
                            DataType = "2",
                            Data = "2",
                        },
                        new TestModel
                        {
                            Name = "3",
                            DataType = "3",
                            Data = "3",
                        },
                    },
                    Dict = new Dictionary<string, ITestModel>
                    {
                        {"4", new TestModel
                                {
                                    Name = "4",
                                    DataType = "4",
                                    Data = "4",
                                }
                         },
                        {"5", new TestModel
                                {
                                    Name = "5",
                                    DataType = "5",
                                    Data = "5",
                                }
                         },
                    }
                };
                #endregion
    
                xmlconfig.Data = new TestConfig()
                {
                    Model = testModel,
                    Dict = new Dictionary<string, ITestModel>()
                    {
                        {"1",testModel },
                        {"2",testModel }
                    },
                    List = new List<ITestModel> { testModel,testModel}
                };
    
    
                #region 动态类型,类,list,字典总和测试
    
                xmlconfig.Data = new DynamicDictionary();
    
                xmlconfig.Data.Name = "Test1";
                xmlconfig.Data.DataType = "Test1";
    
                xmlconfig.Data.List = new List<TestModel>
                {
                    new TestModel
                    {
                        Name = "2",
                        DataType = "2",
                        Data = "2",
                    },
                    new TestModel
                    {
                        Name = "3",
                        DataType = "3",
                        Data = "3",
                    },
                };
                xmlconfig.Data.Dict = new Dictionary<string, TestModel>
                {
                    {
                        "4", new TestModel
                        {
                            Name = "4",
                            DataType = "4",
                            Data = "4",
                        }
                    },
                    {
                        "5", new TestModel
                        {
                            Name = "5",
                            DataType = "5",
                            Data = "5",
                        }
                    },
                };
                xmlconfig.Data.Other.Name = "Test1";
                xmlconfig.Data.Other.DataType = "Test1";
    
                #endregion
    
                xmlconfig.SaveToFile();
    
    
                var data = new XmlConfig();
    
                data.PathOrSourceString = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Configs", "test1.Config");
                data.LoadFromFile();
            }
    

     配置文件为

    <Data Type="dynamic" Classify="Dynamic">
      <Element>
        <Key Type="System.String" Assembly="mscorlib" Classify="Sample">Name</Key>
        <Value Type="System.String" Assembly="mscorlib" Classify="Sample">Test1</Value>
      </Element>
      <Element>
        <Key Type="System.String" Assembly="mscorlib" Classify="Sample">DataType</Key>
        <Value Type="System.String" Assembly="mscorlib" Classify="Sample">Test1</Value>
      </Element>
      <Element>
        <Key Type="System.String" Assembly="mscorlib" Classify="Sample">List</Key>
        <Value Type="System.Collections.IList" Classify="List" GenericType="RunnerTest.Common.TestModel" GenericAssembly="RunnerTest, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null">
          <Element Assembly="RunnerTest" Type="RunnerTest.Common.TestModel" Classify="Custom">
            <Name Type="System.String" Assembly="mscorlib" Classify="Sample">2</Name>
            <DataType Type="System.String" Assembly="mscorlib" Classify="Sample">2</DataType>
            <Data Type="System.String" Assembly="mscorlib" Classify="Sample">2</Data>
          </Element>
          <Element Assembly="RunnerTest" Type="RunnerTest.Common.TestModel" Classify="Custom">
            <Name Type="System.String" Assembly="mscorlib" Classify="Sample">3</Name>
            <DataType Type="System.String" Assembly="mscorlib" Classify="Sample">3</DataType>
            <Data Type="System.String" Assembly="mscorlib" Classify="Sample">3</Data>
          </Element>
        </Value>
      </Element>
      <Element>
        <Key Type="System.String" Assembly="mscorlib" Classify="Sample">Dict</Key>
        <Value Type="System.Collections.IDictionary" Classify="Dictionary" KeyGenericAssembly="mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" KeyGenericType="System.String" ValueGenericAssembly="RunnerTest, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" ValueGenericType="RunnerTest.Common.TestModel">
          <Element>
            <Key Type="System.String" Assembly="mscorlib" Classify="Sample">4</Key>
            <Value Assembly="RunnerTest" Type="RunnerTest.Common.TestModel" Classify="Custom">
              <Name Type="System.String" Assembly="mscorlib" Classify="Sample">4</Name>
              <DataType Type="System.String" Assembly="mscorlib" Classify="Sample">4</DataType>
              <Data Type="System.String" Assembly="mscorlib" Classify="Sample">4</Data>
            </Value>
          </Element>
          <Element>
            <Key Type="System.String" Assembly="mscorlib" Classify="Sample">5</Key>
            <Value Assembly="RunnerTest" Type="RunnerTest.Common.TestModel" Classify="Custom">
              <Name Type="System.String" Assembly="mscorlib" Classify="Sample">5</Name>
              <DataType Type="System.String" Assembly="mscorlib" Classify="Sample">5</DataType>
              <Data Type="System.String" Assembly="mscorlib" Classify="Sample">5</Data>
            </Value>
          </Element>
        </Value>
      </Element>
      <Element>
        <Key Type="System.String" Assembly="mscorlib" Classify="Sample">Other</Key>
        <Value Type="dynamic" Classify="Dynamic">
          <Element>
            <Key Type="System.String" Assembly="mscorlib" Classify="Sample">Name</Key>
            <Value Type="System.String" Assembly="mscorlib" Classify="Sample">Test1</Value>
          </Element>
          <Element>
            <Key Type="System.String" Assembly="mscorlib" Classify="Sample">DataType</Key>
            <Value Type="System.String" Assembly="mscorlib" Classify="Sample">Test1</Value>
          </Element>
        </Value>
      </Element>
    </Data>
    

      

    说明


    最大的用处,你拿到一个对象未知的对象,并不需要知道他的实际类型,就可以进行持久化,并且读取出来之后能够还原到原始类型。


    实现这部分我觉得在于以下几个点

    1. 对元数据的充分理解
    2. 对xml结构的充分理解
    3. 需要一点写算法的能力


    我觉得代码本身并不复杂,只要耐心单步调试都能看懂。

    当然这个是有一定限制的:

    1. 可读性不强,所以在需要从文件进行修改配置比较麻烦

    2.不可跨系统,文件中类型从程序集加载不到时就会出错

    3.性能不高.性能敏感的部分不太适合


    所以这部分功能需要结合业务场景使用,在我这里,包含作业调度系统,统计系统,接口测试工具中有使用.

    这其实特别想WSDL的Soap协议,文件中既包含元数据的说明,又包含数据本身.真个元数据变成也是一个做设计时候一个重要思想。

  • 相关阅读:
    PHP中的error
    回调函数与PHP实例
    PHP的基本入门知识
    Java script OOP——浅谈
    实现单行或多行文本溢出显示省略号
    ECharts
    session management会话管理的原理
    easyui 入门指南
    H5视频/音频
    CSS清除浮动各种方法
  • 原文地址:https://www.cnblogs.com/Skyven/p/7795535.html
Copyright © 2011-2022 走看看