自定義Object與XML互換(序列化)
反射真是個好東西,值得去深入學習.今天利用它實現物件的序列化與反序列化.由於是基於可互換的原則,在沒有找到反序列化之前,拋棄了部分通用的做法.
如果只是簡單的將物件序列化成為XML,本是很方便的,但在考慮需要反序列化後,不得不考慮很多的問題.
比如,序列化值型別(Type.IsValueType為true)的型別時,基本只需要簡單的ToString()就可以.但如果想返序列化的話,有些型別就得去考慮,比如,日期的字串格式.我是通過下面的方式,在確定的區域和日期格式中實現日期與字串之間做轉換:
- static DateTimeFormatInfo dtfi = new CultureInfo("zh-CN", false).DateTimeFormat;
- public override void ToXml(object val, StringBuilder builder)
- {
- builder.AppendFormat("{0}", ((DateTime)val).ToString(dtfi));
- }
- public override Object ToObject(Type type, XmlNode node)
- {
- String val = node.InnerText;
- return DateTime.Parse(val, dtfi);
- }
在實現功能時,集合無疑是最麻煩的,特別有了泛型之後,規則就更復雜了.加上考慮介面型別的話,就難上加難.像在確定集合中元素的型別,也不一樣.如:
陣列元素型別: Type.GetElementType()
泛型的T型別:Type.GetGenericArguments()
而陣列的元素是一個Type,但泛型的T型別卻是Type[].特別在確定System.Collections.CollectionBase的元素型別時,沒有找到可從Type中反射的方法,轉換使用下面的方式確定:
- System.Collections.IEnumerator tor = ((IEnumerable)val).GetEnumerator();
- if (tor.MoveNext())
- {
- Type t = tor.Current.GetType();
- }
這個其實是確定集合元素型別的通用方法,但它在反序列化時卻不能使用.因為這樣做只能在集合不為空時才能使用,而反序列化時,指定的集合卻有可能是空的.所以這個取出來的型別,需要將它的所屬程式集與型別名稱儲存下來,比如在節點中以itype的屬性把它儲存下來.這樣做又增加了程式的複雜度.
同時,在反序列化介面型別的屬性時,也有可能遇到相似的問題,比如如何確定介面的實現型別?這個利用反射是做不到的.那麼又只好把實現型別的相關資訊在序列化時儲存了,那不儲存到ctype屬性值中去吧.
有了一大堆並不完全相容的轉換方法後,如何將程式碼的複雜度儘量降低呢?
在這個序列化與反序列中,我們需要確定哪些轉換方法適用於哪些型別,那麼就讓他主動告訴呼叫者吧.定義一個負責轉換的基類:Switcher,它的作用是定義規則,實現最通用的做法,具體的事務由更詳細的實現類來確定如何進行.下面是Switcher的全部程式碼:
- using System;
- using System.Collections.Generic;
- using System.Text;
- using System.Reflection;
- using System.Xml;
- namespace Guaik.Serialization.Xml
- {
- /// <summary>
- /// 轉換器
- /// </summary>
- public class Switcher
- {
- /// <summary>
- /// 得到型別的屬性列表
- /// </summary>
- public virtual PropertyInfo[] GetProperties(Type t)
- {
- return t.GetProperties();
- }
- /// <summary>
- /// 檢查是否可使用此轉換器
- /// </summary>
- public virtual bool IsSwitcher(Type t)
- {
- return true;
- }
- /// <summary>
- /// 轉換為MXL
- /// </summary>
- public virtual void ToXml(Int32 level, String name, Type type, Object val, StringBuilder builder)
- {
- String sp = " ".PadLeft(level * 2);
- builder.AppendFormat("{0}<{1}>{2}</{1}>", sp, name, val).AppendLine();
- }
- public virtual void ToXml(Object val, StringBuilder builder)
- {
- builder.AppendFormat("{0}", val);
- }
- /// <summary>
- /// 轉換為物件
- /// </summary>
- public virtual Object ToObject(Type type, XmlNode node)
- {
- return Convert.ChangeType(node.InnerText, type);
- }
- }
- }
可以看到,其實主要是實現了三個方法:
IsSwitcher(Type t) 這個用來告訴使用者,你這個轉換器是否適用於Type型別?
ToXml(.....) 這個用來將物件轉換為XML
ToObject(Type type, XmlNode node) 負責將node中的序列化的內容轉換為type型別返回
GetProperties(Type t) 現在只是簡單的呼叫Type型別的方法返回,而以後可以定義相關的規則,可以在序列化時實現隱藏屬性,重新命名等規則
在Switcher中實現的方法都是相對通用的,實現類中,只需要重寫相關的方法則可.比如列舉是不可以像上面那樣,使用Convert.ChangeType來轉換的.因此需要定義一個用於處理列舉的Switcher,如下:
- using System;
- using System.Collections.Generic;
- using System.Text;
- using System.Reflection;
- using System.Xml;
- namespace Guaik.Serialization.Xml
- {
- /// <summary>
- /// 列舉
- /// </summary>
- public class EnumSwitcher : Switcher
- {
- public override bool IsSwitcher(Type t)
- {
- return t.IsEnum;
- }
- public override Object ToObject(Type type, XmlNode node)
- {
- String val = node.InnerText;
- return Enum.Parse(type, val);
- }
- }
- }
實現的內容也非常簡單.由於看到實現一個不相容的型別還是相對方便的. 而在實現將資料序列化與反序列化中,也可以保持程式碼的簡潔,下面是實現轉換的類:
- using System;
- using System.Collections.Generic;
- using System.Text;
- using System.Reflection;
- using System.Xml;
- namespace Guaik.Serialization.Xml
- {
- /// <summary>
- /// XML持久化
- /// </summary>
- public class XmlSerialization
- {
- private static Switcher[] Switcher = new Switcher[] {
- new EnumSwitcher(),
- new StringSwitcher(),
- new DateTimeSwitcher(),
- new ValueTypeSwitcher(),
- //new GenericSwitcher(),
- new CollectionSwitcher()
- };
- private static PropertyInfo FindProperty(Type type, String name)
- {
- PropertyInfo[] infos = GetProperties(type);
- foreach (PropertyInfo info in infos)
- {
- if (info.Name == name) return info;
- }
- return null;
- }
- private static PropertyInfo[] GetProperties(Type t)
- {
- return t.GetProperties();
- }
- private static Switcher GetSwitcher(Type t)
- {
- foreach (Switcher sw in Switcher)
- {
- if (sw.IsSwitcher(t))
- {
- return sw;
- }
- }
- return null;
- }
- #region Xml2Object
- public static Object Xml2Object(Type type, String path)
- {
- XmlDocument doc = new XmlDocument();
- doc.Load(path);
- return Xml2Object(type, doc);
- }
- public static Object Xml2Object(Type type, XmlDocument doc)
- {
- return Xml2Object(type, doc.SelectSingleNode(type.Name));
- }
- public static Object Xml2Object(Type type, XmlNode root)
- {
- Switcher swr = null;
- swr = GetSwitcher(type);
- if (swr != null)
- {
- return swr.ToObject(type, root);
- }
- else
- {
- Object val = null;
- Switcher switcher = null;
- PropertyInfo info = null;
- Object obj = Activator.CreateInstance(type, true);
- foreach (XmlNode node in root.ChildNodes)
- {
- info = FindProperty(type, node.Name);
- if (info != null)
- {
- switcher = GetSwitcher(info.PropertyType);
- val = switcher.ToObject(info.PropertyType, node);
- if (val != null)
- {
- info.GetSetMethod().Invoke(obj, new object[] { val });
- }
- }
- }
- return obj;
- }
- }
- #endregion
- #region Object2Xml
- public static string Object2Xml(Object obj)
- {
- Type t = obj.GetType();
- StringBuilder builder = new StringBuilder();
- builder.AppendLine("<?xml version=/"1.0/" encoding=/"utf-8/" ?> ");
- builder.AppendFormat("<{0}>", t.Name).AppendLine();
- Object2Xml(1, obj, builder);
- builder.AppendFormat("</{0}>", t.Name).AppendLine();
- return builder.ToString();
- }
- public static bool Object2Xml(Int32 level, Object obj, StringBuilder builder)
- {
- Switcher swr = null;
- Type t = obj.GetType();
- swr = GetSwitcher(t);
- if (swr != null)
- {
- swr.ToXml(obj, builder);
- return true;
- }
- else
- {
- object val = null;
- MethodInfo mi = null;
- PropertyInfo[] infos = GetProperties(t);
- foreach (PropertyInfo info in infos)
- {
- mi = info.GetGetMethod();
- if (mi != null)
- {
- val = mi.Invoke(obj, null);
- if (val != null)
- {
- swr = GetSwitcher(val.GetType());
- if (swr != null)
- {
- swr.ToXml(level + 1, info.Name, info.PropertyType, val, builder);
- }
- }
- }
- }
- return false;
- }
- }
- #endregion
- }
- }
在這個類中,有三個重要的方法:
Object2Xml(...) 將物件轉換為XML
Xml2Object(...) 將XML轉換為物件,提供了方法過載,方便使用者
GetSwitcher(..) 確定哪個轉換器可用於當前的轉換
GetSwitcher(..)方法中,使用了一個配置表Switcher ,在這裡儲存可用於轉換的轉換器集合.這個集合的次序確定了轉換器的呼叫.
在呼叫方面,也非常簡單,下面是測試程式碼:
- //型別定義
- public class User
- {
- public string Name { get; set; }
- public bool Sex { get; set; }
- public int Age { get; set; }
- public UserType Type { get; set; }
- public DateTime CreateAt { get; set; }
- public String[] Logs{get;set;}
- public IList<String> List { get; set; }
- public UserCollection Users { get; set; }
- }
- public enum UserType
- {
- Login,
- Logout
- }
- /// <summary>
- /// 資料庫集合
- /// </summary>
- public class UserCollection : CollectionBase
- {
- public User this[int index]
- {
- get
- {
- return ((User)List[index]);
- }
- set
- {
- List[index] = value;
- }
- }
- public User this[string name]
- {
- get
- {
- foreach (User d in this)
- {
- if (d.Name.ToLower() == name.ToLower())
- {
- return (User)d;
- }
- }
- return null;
- }
- }
- public int Add(User value)
- {
- return (List.Add(value));
- }
- public int IndexOf(User value)
- {
- return (List.IndexOf(value));
- }
- public void Insert(int index, User value)
- {
- List.Insert(index, value);
- }
- public void Remove(User value)
- {
- List.Remove(value);
- }
- public bool Contains(User value)
- {
- // If value is not of type User, this will return false.
- return (List.Contains(value));
- }
- protected override void OnInsert(int index, Object value)
- {
- // Insert additional code to be run only when inserting values.
- }
- protected override void OnRemove(int index, Object value)
- {
- // Insert additional code to be run only when removing values.
- }
- protected override void OnSet(int index, Object oldValue, Object newValue)
- {
- // Insert additional code to be run only when setting values.
- }
- protected override void OnValidate(Object value)
- {
- if (!(value is User))
- throw new ArgumentException("value must be of type User.", "value");
- }
- }
- //序列化
- User user = new User();
- user.Name = "鄧銘武";
- user.Sex = true;
- user.Age = 20;
- user.Type = UserType.Logout;
- user.CreateAt = DateTime.Now;
- user.Logs = new string[] {
- "登陸",
- "登出"
- };
- user.List = new List<String>();
- user.List.Add("Name1");
- user.List.Add("Name2");
- user.List.Add("Name3");
- user.Users = new UserCollection();
- User su = new User();
- su.Name = "鄧銘武1";
- user.Users.Add(su);
- su = new User();
- su.Name = "鄧銘武2";
- user.Users.Add(su);
- su = new User();
- su.Name = "鄧銘武3";
- user.Users.Add(su);
- string xml = XmlSerialization.Object2Xml(user);
- richTextBox1.Text = xml;
- //反序列化
- User user = new User();
- XmlDocument doc = new XmlDocument();
- doc.LoadXml(richTextBox1.Text);
- Object obj = (User)XmlSerialization.Xml2Object(typeof(User), doc);
從上面可以看到,序列化與反序列化呼叫時,只需要呼叫一句程式碼就可以實現,過程非常方便.目前它可以支援C#型別系統中的所有基本型別,IList介面,IList<T>介面和CollectionBase集合,陣列等.如果想實現其它型別的轉換操作,可以通過實現Switcher重新配置XmlSerialization.Switcher表就可以達到目的.
相關文章
- java自定義序列化Java
- 實體類與XML序列化與反序列化XML
- 巧用fastjson自定義序列化類實現欄位的轉換ASTJSON
- springbootredis自定義序列化方式(fastJson)Spring BootRedisASTJSON
- Python中巢狀自定義型別的JSON序列化與反序列化Python巢狀型別JSON
- C# - XML讀寫與序列化C#XML
- ajax與XML檔案互動XML
- XML序列化XML
- XML DOM(Document Object Model)XMLObject
- Spring 定時器的使用—Xml、Annotation、自定義Spring定時器XML
- Spring 定時器的使用---Xml、Annotation、自定義Spring定時器XML
- 自定義Navigator切換fragmentFragment
- 自定義值轉換器
- springboot自定義ObjectMapper序列化、配置序列化對LocalDateTime的支援Spring BootObjectAPPLDA
- JSON序列化之旅:深入理解.NET中的JsonResult與自定義ContractResolverJSON
- java 物件與xml相互轉換Java物件XML
- CSS自定義屬性與前端頁面的主題切換CSS前端
- xml序列化和反序列化(一)XML
- 序列化篇 生成xml 以及讀取xmlXML
- Django(6)自定義路由轉換器Django路由
- 如何將自定義XML檢視注入SAP Fiori Elements應用XML
- .NET物件的XML序列化和反序列化物件XML
- xml與陣列的相互轉換——phpXML陣列PHP
- ABP AutoMapper與自定義MappingAPP
- MyBatis使用自定義TypeHandler轉換型別MyBatis型別
- Spring Boot之自定義JSON轉換器Spring BootJSON
- iOS 自定義的卡片流互動控制元件iOS控制元件
- 屬性序列化自定義與字母表排序-JSON框架Jackson精解第3篇排序JSON框架
- 死磕Spring之IoC篇 - 解析自定義標籤(XML 檔案)SpringXML
- 為什麼不建議使用自定義Object作為HashMap的key?ObjectHashMap
- springmvc 自定義訊息轉換器完整例子SpringMVC
- 微信小程式API互動的自定義封裝微信小程式API封裝
- Android中View自定義XML屬性詳解以及R.attr與R.styleable的區別AndroidViewXML
- XML Schema定義XML
- [Flutter翻譯]使用BuiltValueSerializer建立自定義built_value序列化器FlutterUI
- SpringBoot快取管理(三) 自定義Redis快取序列化機制Spring Boot快取Redis
- 使用程式碼給 SAP UI5 XML 檢視新增自定義 CSSUIXMLCSS
- win10怎麼自定義背景圖切換_win10自定義背景圖片隨機切換的步驟Win10隨機
- spark:自定義分割槽,自定義排序,spark與jdbc,廣播變數等Spark排序JDBC變數