自定义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表就可以达到目的.

下面是源代码分享:

Guaik.Serialization.rar

 




 

 

Tag标签: 序列化 反序列化
发表于 2010-02-11 08:03:18 Laodeng 收藏 所属分类: C# 网摘收藏
相关文章: