动态类型序列化
上一篇文章直接就被移除首页了,这次来点大家都能懂的干货.
需求
之前做一个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协议,文件中既包含元数据的说明,又包含数据本身.真个元数据变成也是一个做设计时候一个重要思想。