实体类复制

    随着.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);
    这个方法签名至少可以应对各种值类型和应用类型的情况。

    第二步,就是确定该封装些什么,怎么允许扩展,顺便取个名字

    为了保证功能和扩展性,决定用装饰模式,大概为:

ContractedBlock.gif ExpandedBlockStart.gif 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的键
ContractedBlock.gif ExpandedBlockStart.gif 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服务的基类将变成:

ContractedBlock.gif ExpandedBlockStart.gif 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;
        }

    这样,我们就可以实现自动复制服务了:

ContractedBlock.gif ExpandedBlockStart.gif 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

    }

    来看一个简单的应用:

ContractedBlock.gif ExpandedBlockStart.gif Code
        public class MyClass1
        {
            
public int A { getset; }
        }

        
public class MyClass2
        {
            
public int A { getset; }
        }

        [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,或者值类型到引用类型,或者引用类型到值类型的强大复制功能,例如:

ContractedBlock.gif ExpandedBlockStart.gif Code
        public class MyClass1
        {
            
public int A { getset; }
            
public Nested1 B { getset; }
        }

        
public class Nested1
        {
            
public string C { getset; }
        }

        
public class MyClass2
        {
            
public int A { getset; }
            
public Nested2 B { getset; }
        }

        
public struct Nested2
        {
            
public string C { getset; }
        }

        [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);
        }

    但是,这个简单的复制不是万能的,必须要能够被扩展,前面说过要使用装饰模式来扩展性的功能,那么,先定义个装饰的基类:

ContractedBlock.gif ExpandedBlockStart.gif 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; }
        }
    }

    是不是很简单,如何扩展实现哪?这里就以一个简单的自定义复制器为例:

ContractedBlock.gif ExpandedBlockStart.gif 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);
        }

    来看看怎么用这个自定义复制器:

ContractedBlock.gif ExpandedBlockStart.gif Code
        public class MyClass1
        {
            
public int A { getset; }
        }

        
public class MyClass2
        {
            
public string A { getset; }
        }

        
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
<intstring>(MyConvert);
            MyClass2 c2 
= null;
            copier.Copy
<MyClass1, MyClass2>(c1, ref c2);
            Assert.IsNotNull(c2);
            Assert.AreEqual(c1.A.ToString(), c2.A);

    是不是很简单,当然,还可以写其他很多装饰,例如:数组复制的装饰,枚举复制的装饰,大家可以发挥自己的想象力。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值