Github 开源:高效好用的对象间属性拷贝工具:升讯威 Mapper( Sheng.Mapper)

Github 地址https://github.com/iccb1013/Sheng.Mapper

 

对象属性值映射/拷贝工具。不需要创建映射规则,不要求对象类型一致,适用于简单直接的拷贝操作,可以全属性拷贝,指定属性拷贝,排除指定的属性。拷贝包含 10 个属性的对象 10 万次,耗时 4.x 秒(普通开发机)。

 Github 开源:高效好用的对象间属性拷贝工具:升讯威 Mapper( Sheng.Mapper)

 

+ 拷贝行为只针对 sourceObject 和 targetObject 所共有的属性
+ 在 sourceObject 和 targetObject 中的待拷贝的属性值的类型处理:如果是值类型,直接拷贝,如果是引用类型,sourceObject 中的属性的类型 必须 和 targetObject 中的属性的类型一致,或是它的派生类
+ 如果要支持类型不一致的属性自动进行类型转换,你可以在 PropertyMappingDescription 这个类中实现转换器功能
+ 拷贝行为 不会 改变 targetObject 中不需要被拷贝的属性的值
+ 你可以组合使用几个方法来从多个对象中拷贝指定的属性值到一个 targetObject

和 AutoMapper 互补,与之相比最大优势是短,平,快。不需要创建复杂的映射规则,并支持属性排除操作

 

具体实现:

这里在具体实现上,其实并不复杂,只需对反射操作稍有了解即可,

我们通过 sourceObject 和 targetObject ,获取它们的“类型(Type)”,然后使用 Type.GetProperties() 方法,获取这个对象类型所包含的属性(Property)。

 PropertyInfo[] propertyList = Type.GetProperties();
            foreach (PropertyInfo property in propertyList)
            {
                PropertyMappingDescription propertyMappingDescription = new PropertyMappingDescription(property);

                _propertyList.Add(propertyMappingDescription);
                _propertyNames.Add(property.Name, propertyMappingDescription);
            }

这里有另外一个细节需要留意的是,我们要把同样类型(Type)的相关信息,缓存起来,这样下次再拷贝相同类型的对象时,就无需再去反射它的 Properties。

我们通过 TypeMappingDescription 对对象的类型信息进行缓存和包装,提供我们所需要的基本操作:

public bool ContainsProperty(string name)
        {
            if (String.IsNullOrEmpty(name))
                throw new ArgumentNullException("TypeMappingDescription.ContainsProperty 必须指定属性名。");

            return _propertyNames.ContainsKey(name);
        }       

        public object GetValue(object obj, string propertyName)
        {
            if (obj == null)
                throw new ArgumentNullException("指定的对象为空。");

            if (obj.GetType() != this.Type)
                throw new ArgumentException("指定的对象类型与缓存的对象类型不一致。");

            if (_propertyNames.ContainsKey(propertyName) == false)
                throw new ArgumentOutOfRangeException("指定的属性名不存在。");

            PropertyMappingDescription propertyMappingDescription = (PropertyMappingDescription)_propertyNames[propertyName];
            if (propertyMappingDescription.CanRead == false)
                throw new InvalidOperationException("属性 " + propertyName + "不可读。");

            return propertyMappingDescription.GetValue(obj);
        }

        public void SetValue(object obj, string propertyName, object value)
        {
            if (obj == null)
                throw new ArgumentNullException("指定的对象为空。");

            if (obj.GetType() != this.Type)
                throw new ArgumentException("指定的对象类型与缓存的对象类型不一致。");

            if (_propertyNames.ContainsKey(propertyName) == false)
                throw new ArgumentOutOfRangeException("指定的属性名不存在。");

            PropertyMappingDescription propertyMappingDescription = (PropertyMappingDescription)_propertyNames[propertyName];
            if (propertyMappingDescription.CanWrite == false)
                throw new InvalidOperationException("属性 " + propertyName + "只读。");

            Type propertyType = propertyMappingDescription.PropertyInfo.PropertyType;
            if (propertyType.IsValueType == false && value != null)
            {
                Type valueType = value.GetType();
                if(propertyType != valueType && valueType.IsSubclassOf(propertyType) == false)
                {
                    throw new ArgumentException("目标对象的 " + propertyName + "与 value 的类型既不一致,也不是目标类型的派生类。");
                }
            }

            propertyMappingDescription.SetValue(obj, value);
        }

同时我们使用 PropertyMappingDescription 对 PropertyInfo 进行封装。对 PropertyInfo 进行封装,是为了方便我们后续针对属性添加属性值的转换器,以便实现稍复杂一些的属性拷贝操作。

最后我们来测试一下拷贝操作:

 A a = new A()
            {
                Name = "张三",
                Age = 10,
                Class = "一班",
                CObject = new SubC()
                {
                    Message = "Hello"
                },
                P1 = "1",
                P2 = "2",
                P3 = "3",
                P4 = "4",
                P5 = "5",
                P6 = "6"
            };

            B b = new B();

            Stopwatch stopwatch = new Stopwatch();
            stopwatch.Start();

            for (int i = 0; i < 100000; i++)
            {
                //全部属性拷贝
                ShengMapper.SetValues(a, b);

                //拷贝指定的属性
               // ShengMapper.SetValuesWithProperties(a, b, new string[] { "Name", "Age", "P1" });

                //排除指定的属性
                //ShengMapper.SetValuesWithoutProperties(a, b, new string[] { "Name", "Age", "P1" });

                
            }

            stopwatch.Stop();

            Console.WriteLine("对包含 10 个属性的对象的属性值拷贝 10 万次,耗时:" + stopwatch.Elapsed.ToString());

            Console.ReadLine();

我模拟了一几个类,他们有不同类型的属性,还包括引用类型的属性我派生类。

对于包含 10 个属性的类的 10 万次属性值拷贝,在开发机上大约用了 4.x 秒。

完整的代码位于 Github。

https://github.com/iccb1013/Sheng.Mapper