Github 開源:高效好用的對象間屬性拷貝工具:升訊威 Mapper

GitHub 技術 科技優家 2017-06-18

對象屬性值映射/拷貝工具。不需要創建映射規則,不要求對象類型一致,適用於簡單直接的拷貝操作,可以全屬性拷貝,指定屬性拷貝,排除指定的屬性。拷貝包含 10 個屬性的對象 10 萬次,耗時 4.x 秒(普通開發機)。

Github 開源:高效好用的對象間屬性拷貝工具:升訊威 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。

相關推薦

推薦中...