針對 List<T> 之間屬性複製,透過反射配合 Dictionary 快取 PropertyMapper 避免重複 GetProperties(),支援同名同型屬性單向複製(來源可讀、目標可寫)。
public static class ObjectMapper {
// 快取:Key = "SourceFullName_TargetFullName",Value = 屬性映射清單
static readonly Dictionary<string, List<PropertyMapper>> _cache
= new Dictionary<string, List<PropertyMapper>>();
// 公開進入點:集合複製
public static void CopyProperties<TSource, TTarget>(List<TSource> src, List<TTarget> dst)
where TTarget : new() {
if (src == null || dst == null) return;
foreach (var s in src) {
var t = new TTarget();
CopyProperties(s, t);
dst.Add(t);
}
}
// 單一物件複製
public static void CopyProperties(object src, object dst) {
if (src == null || dst == null) return;
var key = $"{src.GetType().FullName}_{dst.GetType().FullName}";
if (!_cache.ContainsKey(key)) BuildMapper(src.GetType(), dst.GetType());
foreach (var pm in _cache[key]) {
var val = pm.Source.GetValue(src);
pm.Target.SetValue(dst, val);
}
}
// 建立映射:同名、同型、可讀/可寫
static void BuildMapper(Type srcType, Type dstType) {
var key = $"{srcType.FullName}_{dstType.FullName}";
if (_cache.ContainsKey(key)) return;
var maps = from sp in srcType.GetProperties()
join tp in dstType.GetProperties() on sp.Name equals tp.Name
where sp.CanRead && tp.CanWrite && sp.PropertyType == tp.PropertyType
select new PropertyMapper { Source = sp, Target = tp };
_cache[key] = maps.ToList();
}
class PropertyMapper {
public PropertyInfo Source { get; set; }
public PropertyInfo Target { get; set; }
}
}
[TestMethod]
public void CopyProperties_List_Test() {
var src = Enumerable.Range(0, 10).Select(i => new SrcEntity {
Name = "jasen", Time = DateTime.Parse("2011-5-13"), Flag = true, Option = ConflictOption.OverwriteChanges, Index = i
}).ToList();
var dst = new List<DstEntity>();
ObjectMapper.CopyProperties(src, dst);
for (int i = 0; i < 10; i++) {
Assert.AreEqual(src[i].Name, dst[i].Name);
Assert.AreEqual(src[i].Time, dst[i].Time);
Assert.AreEqual(src[i].Flag, dst[i].Flag);
Assert.AreEqual(src[i].Option, dst[i].Option);
}
}
class SrcEntity {
public string Name { get; set; }
public DateTime Time { get; set; }
public bool Flag { get; set; }
public ConflictOption Option { get; set; }
public int Index { get; set; }
}
class DstEntity {
public string Name { get; set; }
public DateTime Time { get; set; }
public bool Flag { get; set; }
public ConflictOption Option { get; set; }
public int Index { get; set; }
}
PropertyMapper 清單GetValue/SetValue 編譯為強型別委派,提升 10x+ 效能(本範例未實作)IList,可遞迴呼叫 CopyProperties 處理