随着.net引入attribute,实体类在编程中的重要性已经越来越重要了,并且有越来越多的工具开始生成自己的实体类。但是这也引来了一个麻烦,每个工具生成的实体类需要各自的attribute,并且可能互相不通用。
例如:Entity Framework生成的实体类有:Serializable,DataContract,EdmEntityType等,属性有DataMember,EdmScalarProperty等,entity framework所需要的各种特性,并且附带了wcf、xml序列化所需要的特性。但是,当你需要把这些实体做一个特殊的操作,并且这个操作又需要一些特殊的attribute时,至少有下面两种选择:
1、直接修改生成的实体类,但是这样做了以后,通常会因为重新生成一边实体类而丢失了所有已经标记的attribute,这几乎让人无法接受。
2、再定义一套实体类,但是,这样做的缺点是你需要手动的copy每一个值,这个工作有让人感到麻烦而无奈。
所以,我们不得不寻找其他方案:
1、找一个可以自己控制的代码生成器(例如:codesmith),但是,总有那些用现成工具很难配置的代码生成。
2、运用反射,根据某种契约(例如:属性名称相同)自动复制数据,但是,传统反射的效率是在是一个大问题。
但是,别忘了反射可以直接Emit出IL,这样可以极大提高复制速度,达到接近直接代码手写手动代码copy的效率,本文就是提供了这么一种简单的实现。
第一步,就是确定核心功能Copy方法的签名,
刚开始定义成了:
void
Copy
<
T, TResult
>
(T value, TResult result);
但是,不久就发现这个定义在很多场合不适合,例如TResult为值类型时,那么对TResult的任何复制操作将都是白搭。
又经过了几次修订以后,最终确定为:
void
Copy
<
T, TResult
>
(T value,
ref
TResult result);
这个方法签名至少可以应对各种值类型和应用类型的情况。
第二步,就是确定该封装些什么,怎么允许扩展,顺便取个名字。
为了保证功能和扩展性,决定用装饰模式,大概为:
Code
public abstract class EntityCopier
{
public abstract void Copy<TSource, TResult>(TSource source, ref TResult result);
public static EntityCopier CreateAuto()
{
threw new NotImplementedException();
}
}
当然,还要会基类中增加Cache服务,把Emit出来的方法cache下来,提高复制效率。当然,先要定义Cache的值(当然和Copy方法的签名一致)
public
delegate
void
CopyDelegate
<
TSource, TResult
>
(TSource source,
ref
TResult result);
以及Cache的键
Code
internal struct CacheKey
{
public readonly EntityCopier Copier;
public readonly Type Source;
public readonly Type Result;
public CacheKey(EntityCopier copier, Type source, Type result)
{
Copier = copier;
Source = source;
Result = result;
}
}
然后,拥有Cache服务的基类将变成:
Code
public abstract class EntityCopier
{
private Dictionary<CacheKey, Delegate> _dict =
new Dictionary<CacheKey, Delegate>();
public abstract void Copy<TSource, TResult>(TSource source, ref TResult result);
internal protected virtual bool CopyCore<TSource, TResult>(EntityCopier copier, TSource source, ref TResult result)
{
CopyDelegate<TSource, TResult> cd = GetDelegate<TSource, TResult>(copier);
if (cd == null)
return false;
cd(source, ref result);
return true;
}
internal protected abstract CopyDelegate<TSource, TResult> CreateCopyDelegate<TSource, TResult>(EntityCopier copier);
private CopyDelegate<TSource, TResult> GetDelegate<TSource, TResult>(EntityCopier copier)
{
Delegate d = null;
CacheKey key = new CacheKey(copier, typeof(TSource), typeof(TResult));
if (_dict.TryGetValue(key, out d))
return (CopyDelegate<TSource, TResult>)d;
CopyDelegate<TSource, TResult> cd = CreateCopyDelegate<TSource, TResult>(copier);
_dict[key] = cd;
return cd;
}
public static EntityCopier CreateAuto()
{
throw new NotImplementedException();
}
}
哦,对了,在这里,无论TSource还是TResult必须要是公开类型,否则的话,会遇上权限问题,所以,在基类里面加一个方法:
protected
static
bool
CheckType(Type type)
{
return
type.IsVisible;
}
这样,我们就可以实现自动复制服务了:
Code
// 初步成型的EntityCopier和自动复制的实现
public abstract class EntityCopier
{
#region Fields
private static MethodInfo _copyMethod;
private Dictionary<CacheKey, Delegate> _dict =
new Dictionary<CacheKey, Delegate>();
#endregion
#region Internal Methods
internal void RegistCore<TSource, TResult>(EntityCopier copier, CopyDelegate<TSource, TResult> cd)
{
_dict[new CacheKey(copier, typeof(TSource), typeof(TResult))] = cd;
}
internal static void CheckSourceNull(ILGenerator gen, Type sourceType)
{
if (!sourceType.IsValueType)
{
Label notNull = gen.DefineLabel();
gen.Emit(OpCodes.Ldarg_1);
gen.Emit(OpCodes.Brtrue_S, notNull);
gen.Emit(OpCodes.Ret);
gen.MarkLabel(notNull);
}
else if (sourceType.IsGenericType &&
sourceType.GetGenericTypeDefinition() == typeof(Nullable<>))
{
Label notNull = gen.DefineLabel();
gen.Emit(OpCodes.Ldarg_1);
gen.Emit(OpCodes.Box, sourceType);
gen.Emit(OpCodes.Brtrue_S, notNull);
gen.Emit(OpCodes.Ret);
gen.MarkLabel(notNull);
}
}
#endregion
#region Virtual Methods
public abstract void Copy<TSource, TResult>(TSource source, ref TResult result);
internal protected virtual bool CopyCore<TSource, TResult>(EntityCopier copier, TSource source, ref TResult result)
{
CopyDelegate<TSource, TResult> cd = GetDelegate<TSource, TResult>(copier);
if (cd == null)
return false;
cd(source, ref result);
return true;
}
internal protected abstract CopyDelegate<TSource, TResult> CreateCopyDelegate<TSource, TResult>(EntityCopier copier);
#endregion
#region Properties
protected static MethodInfo CopyMethod
{
get
{
return _copyMethod ??
(_copyMethod = typeof(EntityCopier).GetMethod("Copy"));
}
}
public virtual bool CanBeDecorated
{
get { return true; }
}
#endregion
#region Private Implements
private CopyDelegate<TSource, TResult> GetDelegate<TSource, TResult>(EntityCopier copier)
{
Delegate d = null;
CacheKey key = new CacheKey(copier, typeof(TSource), typeof(TResult));
if (_dict.TryGetValue(key, out d))
return (CopyDelegate<TSource, TResult>)d;
CopyDelegate<TSource, TResult> cd = CreateCopyDelegate<TSource, TResult>(copier);
_dict[key] = cd;
return cd;
}
#endregion
#region Static Methods
public static EntityCopier CreateAuto()
{
return new AutoCopier();
}
protected static bool CheckType(Type type)
{
return type.IsVisible;
}
#endregion
}
//以及自动复制的实现(估计看了就会头晕,呵呵)
internal class AutoCopier
: EntityCopier
{
#region Sub-Classes
protected sealed class ContextInfo
{
internal EntityCopier Copier;
internal Type SourceType;
internal Type ResultType;
internal ILGenerator ILGen;
internal bool SourceIsValueType;
internal bool ResultIsValueType;
}
protected struct PropertyPair
{
public readonly PropertyInfo SourceProperty;
public readonly PropertyInfo ResultProperty;
public PropertyPair(PropertyInfo sourceProperty, PropertyInfo resultProperty)
{
SourceProperty = sourceProperty;
ResultProperty = resultProperty;
}
}
#endregion
#region Overrides
public sealed override void Copy<TSource, TResult>(TSource source, ref TResult result)
{
if (!CheckType(typeof(TSource)))
throw new ArgumentException("type cannot be non-public type", typeof(TSource).ToString());
if (!CheckType(typeof(TResult)))
throw new ArgumentException("type cannot be non-public type", typeof(TResult).ToString());
CopyCore<TSource, TResult>(this, source, ref result);
}
protected internal sealed override CopyDelegate<TSource, TResult> CreateCopyDelegate<TSource, TResult>(EntityCopier copier)
{
ContextInfo info = new ContextInfo();
info.Copier = copier;
info.SourceType = typeof(TSource);
info.ResultType = typeof(TResult);
info.SourceIsValueType = info.SourceType.IsValueType;
info.ResultIsValueType = info.ResultType.IsValueType;
return (CopyDelegate<TSource, TResult>)CreateCopyDelegateCore(info);
}
#endregion
#region Private Implements
private Delegate CreateCopyDelegateCore(ContextInfo info)
{
Type[] args = new Type[3];
args[0] = typeof(EntityCopier);
args[1] = info.SourceType;
args[2] = info.ResultType.MakeByRefType();
DynamicMethod dm = new DynamicMethod(string.Empty, typeof(void), args);
info.ILGen = dm.GetILGenerator();
CreateCopyDelegateBody(info);
info.ILGen.Emit(OpCodes.Ret);
return dm.CreateDelegate(typeof(CopyDelegate<,>).MakeGenericType(info.SourceType, info.ResultType), info.Copier);
}
private void CreateCopyDelegateBody(ContextInfo info)
{
CheckSourceNull(info);
if (info.SourceType == info.ResultType)
CopySameType(info);
else
CopyDifferentType(info);
info.ILGen.Emit(OpCodes.Ret);
}
private static void CheckSourceNull(ContextInfo info)
{
if (!info.SourceIsValueType)
{
Label notNull = info.ILGen.DefineLabel();
info.ILGen.Emit(OpCodes.Ldarg_1);
info.ILGen.Emit(OpCodes.Brtrue_S, notNull);
info.ILGen.Emit(OpCodes.Ret);
info.ILGen.MarkLabel(notNull);
}
else if (info.SourceType.IsGenericType &&
info.SourceType.GetGenericTypeDefinition() == typeof(Nullable<>))
{
Label notNull = info.ILGen.DefineLabel();
info.ILGen.Emit(OpCodes.Ldarg_1);
info.ILGen.Emit(OpCodes.Box, info.SourceType);
info.ILGen.Emit(OpCodes.Brtrue_S, notNull);
info.ILGen.Emit(OpCodes.Ret);
info.ILGen.MarkLabel(notNull);
}
}
private static void CopySameType(ContextInfo info)
{
info.ILGen.Emit(OpCodes.Ldarg_2);
info.ILGen.Emit(OpCodes.Ldarg_1);
info.ILGen.Emit(OpCodes.Stobj, info.ResultType);
}
private void CopyDifferentType(ContextInfo info)
{
EnsureResultInstance(info);
foreach (PropertyPair item in GetPropertyPair(info))
CopyProperty(info, item);
}
private static void EnsureResultInstance(ContextInfo info)
{
if (!info.ResultIsValueType)
{
Label label = info.ILGen.DefineLabel();
LoadResult(info);
info.ILGen.Emit(OpCodes.Brtrue_S, label);
TryCreateResultInstance(info);
info.ILGen.MarkLabel(label);
}
}
private static void TryCreateResultInstance(ContextInfo info)
{
ConstructorInfo ctor = info.ResultType.GetConstructor(Type.EmptyTypes);
if (ctor == null)
{
info.ILGen.Emit(OpCodes.Ret);
}
else
{
info.ILGen.Emit(OpCodes.Ldarg_2);
info.ILGen.Emit(OpCodes.Newobj, ctor);
info.ILGen.Emit(OpCodes.Stind_Ref);
}
}
private static void CopyProperty(ContextInfo info, PropertyPair item)
{
Label skip = info.ILGen.DefineLabel();
LocalBuilder lb = info.ILGen.DeclareLocal(item.ResultProperty.PropertyType);
if (item.ResultProperty.PropertyType.IsValueType)
{
info.ILGen.Emit(OpCodes.Ldloca, lb);
info.ILGen.Emit(OpCodes.Initobj, item.ResultProperty.PropertyType);
}
else
{
info.ILGen.Emit(OpCodes.Ldnull);
info.ILGen.Emit(OpCodes.Stloc, lb);
}
LoadResult(info);
info.ILGen.Emit(OpCodes.Ldarg_0);
LoadSourceProperty(info, item);
if (!item.SourceProperty.PropertyType.IsValueType)
{
Label notNull = info.ILGen.DefineLabel();
info.ILGen.Emit(OpCodes.Dup);
info.ILGen.Emit(OpCodes.Brtrue_S, notNull);
info.ILGen.Emit(OpCodes.Pop);
info.ILGen.Emit(OpCodes.Pop);
info.ILGen.Emit(OpCodes.Pop);
info.ILGen.Emit(OpCodes.Br_S, skip);
info.ILGen.MarkLabel(notNull);
}
info.ILGen.Emit(OpCodes.Ldloca, lb);
info.ILGen.Emit(OpCodes.Callvirt, CopyMethod.MakeGenericMethod(item.SourceProperty.PropertyType, item.ResultProperty.PropertyType));
info.ILGen.Emit(OpCodes.Ldloc, lb);
SetResultProperty(info, item);
info.ILGen.MarkLabel(skip);
}
protected virtual IEnumerable<PropertyPair> GetPropertyPair(ContextInfo info)
{
PropertyInfo[] srcProps = info.SourceType.GetProperties(BindingFlags.Public | BindingFlags.Instance);
PropertyInfo[] resultProps = info.ResultType.GetProperties(BindingFlags.Public | BindingFlags.Instance);
foreach (PropertyInfo resultProp in resultProps)
{
if (resultProp.GetIndexParameters().Length > 0)
continue;
MethodInfo setMethod = resultProp.GetSetMethod(false);
if (setMethod == null)
continue;
foreach (PropertyInfo srcProp in srcProps)
{
if (srcProp.Name != resultProp.Name)
continue;
if (srcProp.GetIndexParameters().Length > 0)
continue;
if (srcProp.GetGetMethod(false) == null)
continue;
yield return new PropertyPair(srcProp, resultProp);
break;
}
}
}
private static void LoadResult(ContextInfo info)
{
info.ILGen.Emit(OpCodes.Ldarg_2);
if (!info.ResultIsValueType)
{
info.ILGen.Emit(OpCodes.Ldind_Ref);
info.ILGen.Emit(OpCodes.Castclass, info.ResultType);
}
}
private static void LoadSourceProperty(ContextInfo info, PropertyPair item)
{
if (info.SourceIsValueType)
{
info.ILGen.Emit(OpCodes.Ldarga_S, 1);
info.ILGen.Emit(OpCodes.Call, item.SourceProperty.GetGetMethod(false));
}
else
{
info.ILGen.Emit(OpCodes.Ldarg_1);
info.ILGen.Emit(OpCodes.Callvirt, item.SourceProperty.GetGetMethod(false));
}
}
private static void SetResultProperty(ContextInfo info, PropertyPair item)
{
if (info.ResultIsValueType)
info.ILGen.Emit(OpCodes.Call, item.ResultProperty.GetSetMethod(false));
else
info.ILGen.Emit(OpCodes.Callvirt, item.ResultProperty.GetSetMethod(false));
}
#endregion
}
来看一个简单的应用:
Code
public class MyClass1
{
public int A { get; set; }
}
public class MyClass2
{
public int A { get; set; }
}
[TestMethod]
public void Test()
{
MyClass1 c1 = new MyClass1();
c1.A = 1;
MyClass2 c2 = null;
EntityCopier copier = EntityCopier.CreateAuto();
copier.Copy<MyClass1, MyClass2>(c1, ref c2);
Assert.IsNotNull(c2);
Assert.AreEqual(c1.A, c2.A);
}
看起来不错,基本的属性对属性的复制已经没问题了,而且,还可以支持Nested,或者值类型到引用类型,或者引用类型到值类型的强大复制功能,例如:
Code
public class MyClass1
{
public int A { get; set; }
public Nested1 B { get; set; }
}
public class Nested1
{
public string C { get; set; }
}
public class MyClass2
{
public int A { get; set; }
public Nested2 B { get; set; }
}
public struct Nested2
{
public string C { get; set; }
}
[TestMethod]
public void Test()
{
MyClass1 c1 = new MyClass1
{
A = 1,
B = new Nested1 { C = "Hello world", },
};
EntityCopier copier = EntityCopier.CreateAuto();
MyClass2 c2 = null;
copier.Copy<MyClass1, MyClass2>(c1, ref c2);
Assert.IsNotNull(c2);
Assert.AreEqual(c1.A, c2.A);
Assert.AreEqual(c1.B.C, c2.B.C);
}
但是,这个简单的复制不是万能的,必须要能够被扩展,前面说过要使用装饰模式来扩展性的功能,那么,先定义个装饰的基类:
Code
public abstract class CopierDecorator
: EntityCopier
{
private readonly EntityCopier _inner;
protected CopierDecorator(EntityCopier inner)
{
if (inner == null)
throw new ArgumentNullException("inner");
if (!inner.CanBeDecorated)
throw new InvalidOperationException("inner cannot be decorated");
_inner = inner;
}
public sealed override void Copy<TSource, TResult>(TSource source, ref TResult result)
{
if (!CheckType(typeof(TSource)))
throw new ArgumentException("type cannot be non-public type", typeof(TSource).ToString());
if (!CheckType(typeof(TResult)))
throw new ArgumentException("type cannot be non-public type", typeof(TResult).ToString());
CopyCore<TSource, TResult>(this, source, ref result);
}
internal protected sealed override bool CopyCore<TSource, TResult>(
EntityCopier copier, TSource source, ref TResult result)
{
if (base.CopyCore<TSource, TResult>(copier, source, ref result))
return true;
return _inner.CopyCore<TSource, TResult>(copier, source, ref result);
}
public EntityCopier Inner
{
get { return _inner; }
}
}
是不是很简单,如何扩展实现哪?这里就以一个简单的自定义复制器为例:
Code
public class CustomCopierDecorator
: CopierDecorator
{
public CustomCopierDecorator(EntityCopier inner)
: base(inner) { }
public void Register<TSource, TResult>(CopyDelegate<TSource, TResult> cd)
{
if (cd == null)
throw new ArgumentNullException("cd");
RegistCore<TSource, TResult>(this, cd);
}
来看看怎么用这个自定义复制器:
Code
public class MyClass1
{
public int A { get; set; }
}
public class MyClass2
{
public string A { get; set; }
}
public static void MyConvert(int value, ref string result)
{
result = value.ToString();
}
[TestMethod]
public void Test()
{
MyClass1 c1 = new MyClass1 { A = 1, };
CustomCopierDecorator copier = new CustomCopierDecorator(EntityCopier.CreateAuto());
copier.Register<int, string>(MyConvert);
MyClass2 c2 = null;
copier.Copy<MyClass1, MyClass2>(c1, ref c2);
Assert.IsNotNull(c2);
Assert.AreEqual(c1.A.ToString(), c2.A);
是不是很简单,当然,还可以写其他很多装饰,例如:数组复制的装饰,枚举复制的装饰,大家可以发挥自己的想象力。