Github 地址:https://github.com/iccb1013/Sheng.Mapper |
对象属性值映射/拷贝工具。不需要创建映射规则,不要求对象类型一致,适用于简单直接的拷贝操作,可以全属性拷贝,指定属性拷贝,排除指定的属性。拷贝包含 10 个属性的对象 10 万次,耗时 4.x 秒(普通开发机)。
+ 拷贝行为只针对 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