上一篇文章直接就被移除首頁了,此次來點你們都能懂的乾貨.算法
需求ide
以前作一個winform的工具時候有如下幾個需求
1. 主窗體(或者叫平臺)能夠安裝若干類型的插件。
2. 插件關閉時候須要保存狀態。
3. 插件加載的時候能夠加載上次關閉的配置。
4. 插件中的配置能夠切換。
5. 主窗體自己保存當前插件,而且能夠經過不一樣的配置文件切換插件工具
使用上最方便的作法是將配置給平臺來管理。可是平臺自己並不知道插件要保存怎樣的配置。針對以上問題在配置這個上作了以下設計性能
設計測試
1. 動態類型序列化以知足插件的任何配置須要
2. 動態類型基本的就是dynamic,那麼咱們需用字典做爲實現
3. 支持具體的類進行序列化,那麼此時須要用xml保存類的元數據信息
4. 支持接口的序列化,此時也是保存實際類型的元數據信息
5. 支持List序列化
6. 支持Arry序列化
7. 支持Dictionary序列化this
接口定義spa
其中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協議,文件中既包含元數據的說明,又包含數據自己.真個元數據變成也是一個作設計時候一個重要思想。