實體類編程的最佳伴侶——高效的實體類復制


在面向對象編程的時代,實體類的操作已經越來越普遍了,從數據庫中獲取數據,離線存儲在實體類集合中,對集合的修改再反饋到數據庫中等。對於實體類的修改就意味着對數據庫的修改,為了更好的自動化編程,減少編碼量,同時也為了提高運行速度,這里發布個代碼類——對象的深度拷貝對象屬性的淺拷貝, 利用了Emit操作,執行速度肯定是最快的。

ObjectCopy
public static class ObjectCopy
{
    struct Identity
    {
        int _hashcode;
        RuntimeTypeHandle _type;

        public Identity(int hashcode, RuntimeTypeHandle type)
        {
            _hashcode = hashcode;
            _type = type;
        }
    }
    //緩存對象復制的方法。
    static Dictionary<Type, Func<object, Dictionary<Identity, object>, object>> methods1 = new Dictionary<Type, Func<object, Dictionary<Identity, object>, object>>();
    static Dictionary<Type, Action<object, Dictionary<Identity, object>, object>> methods2 = new Dictionary<Type, Action<object, Dictionary<Identity, object>, object>>();
        static Dictionary<Type, Action<object, Dictionary<Identity, object>, object>> methods3 = new Dictionary<Type, Action<object, Dictionary<Identity, object>, object>>();

    static List<FieldInfo> GetSettableFields(Type t)
    {
        return t.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance).ToList();
    }

    class PropInfo
    {
        public string Name { get; set; }
        public MethodInfo Getter { get; set; }
        public MethodInfo Setter { get; set; }
        public Type Type { get; set; }
    }

    static List<PropInfo> GetPublicProps(Type t)
    {
        return t
                .GetProperties()
                .Select(p => new PropInfo
                {
                    Name = p.Name,
                    Getter = p.DeclaringType == t ? p.GetGetMethod(true) : p.DeclaringType.GetProperty(p.Name).GetGetMethod(true),
                    Setter = p.DeclaringType == t ? p.GetSetMethod(true) : p.DeclaringType.GetProperty(p.Name).GetSetMethod(true),
                    Type = p.PropertyType
                })
                .Where(info => info.Getter != null && info.Setter != null)
                .ToList();
    }

    static Func<object, Dictionary<Identity, object>, object> CreateCloneMethod1(Type type, Dictionary<Identity, object> objects)
    {
        Type tmptype;
        var fields = GetSettableFields(type);
        var dm = new DynamicMethod(string.Format("Clone{0}", Guid.NewGuid()), typeof(object), new[] { typeof(object), typeof(Dictionary<Identity, object>) }, true);
        var il = dm.GetILGenerator();
        il.DeclareLocal(type);
        il.DeclareLocal(type);
        il.DeclareLocal(typeof(Identity));
        if (!type.IsArray)
        {
            il.Emit(OpCodes.Newobj, type.GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null));
            il.Emit(OpCodes.Dup);
            il.Emit(OpCodes.Stloc_1);
            il.Emit(OpCodes.Ldloca_S, 2);
            il.Emit(OpCodes.Ldarg_0);
            il.Emit(OpCodes.Castclass, type);
            il.Emit(OpCodes.Dup);
            il.Emit(OpCodes.Stloc_0);
            il.Emit(OpCodes.Callvirt, typeof(object).GetMethod("GetHashCode"));
            il.Emit(OpCodes.Ldtoken, type);
            il.Emit(OpCodes.Call, typeof(Identity).GetConstructor(BindingFlags.Instance | BindingFlags.Public, null, new[] { typeof(int), typeof(RuntimeTypeHandle) }, null));
            il.Emit(OpCodes.Ldarg_1);
            il.Emit(OpCodes.Ldloc_2);
            il.Emit(OpCodes.Ldloc_1);
            il.Emit(OpCodes.Callvirt, typeof(Dictionary<Identity, object>).GetMethod("Add"));
            foreach (var field in fields)
            {
                if (!field.FieldType.IsValueType && field.FieldType != typeof(String))
                {
                    //不符合條件的字段,直接忽略,避免報錯。
                    if ((field.FieldType.IsArray && (field.FieldType.GetArrayRank() > 1 || (!(tmptype = field.FieldType.GetElementType()).IsValueType && tmptype != typeof(String) && tmptype.GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null) == null))) ||
                        (!field.FieldType.IsArray && field.FieldType.GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null) == null))
                        break;
                    il.Emit(OpCodes.Ldloc_1);
                    il.Emit(OpCodes.Ldloc_0);
                    il.Emit(OpCodes.Ldfld, field);
                    il.Emit(OpCodes.Ldarg_1);
                    il.EmitCall(OpCodes.Call, typeof(ObjectCopy).GetMethod("CopyImpl1", BindingFlags.NonPublic | BindingFlags.Static).MakeGenericMethod(field.FieldType), null);
                    il.Emit(OpCodes.Stfld, field);
                }
                else
                {
                    il.Emit(OpCodes.Ldloc_1);
                    il.Emit(OpCodes.Ldloc_0);
                    il.Emit(OpCodes.Ldfld, field);
                    il.Emit(OpCodes.Stfld, field);
                }
            }
            for (type = type.BaseType; type != null && type != typeof(object); type = type.BaseType)
            {
                //只需要查找基類的私有成員,共有或受保護的在派生類中直接被復制過了。
                fields = type.GetFields(BindingFlags.NonPublic | BindingFlags.Instance).ToList();
                foreach (var field in fields)
                {
                    if (!field.FieldType.IsValueType && field.FieldType != typeof(String))
                    {
                        //不符合條件的字段,直接忽略,避免報錯。
                        if ((field.FieldType.IsArray && (field.FieldType.GetArrayRank() > 1 || (!(tmptype = field.FieldType.GetElementType()).IsValueType && tmptype != typeof(String) && tmptype.GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null) == null))) ||
                            (!field.FieldType.IsArray && field.FieldType.GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null) == null))
                            break;
                        il.Emit(OpCodes.Ldloc_1);
                        il.Emit(OpCodes.Ldloc_0);
                        il.Emit(OpCodes.Ldfld, field);
                        il.Emit(OpCodes.Ldarg_1);
                        il.EmitCall(OpCodes.Call, typeof(ObjectCopy).GetMethod("CopyImpl1", BindingFlags.NonPublic | BindingFlags.Static).MakeGenericMethod(field.FieldType), null);
                        il.Emit(OpCodes.Stfld, field);
                    }
                    else
                    {
                        il.Emit(OpCodes.Ldloc_1);
                        il.Emit(OpCodes.Ldloc_0);
                        il.Emit(OpCodes.Ldfld, field);
                        il.Emit(OpCodes.Stfld, field);
                    }
                }
            }
        }
        else
        {
            Type arraytype = type.GetElementType();
            var i = il.DeclareLocal(typeof(int));
            var lb1 = il.DefineLabel();
            var lb2 = il.DefineLabel();
            il.Emit(OpCodes.Ldarg_0);
            il.Emit(OpCodes.Castclass, type);
            il.Emit(OpCodes.Dup);
            il.Emit(OpCodes.Stloc_0);
            il.Emit(OpCodes.Ldlen);
            il.Emit(OpCodes.Dup);
            il.Emit(OpCodes.Ldc_I4_1);
            il.Emit(OpCodes.Sub);
            il.Emit(OpCodes.Stloc, i);
            il.Emit(OpCodes.Newarr, arraytype);
            il.Emit(OpCodes.Dup);
            il.Emit(OpCodes.Stloc_1);
            il.Emit(OpCodes.Ldloca_S, 2);
            il.Emit(OpCodes.Ldloc_0);
            il.Emit(OpCodes.Callvirt, typeof(object).GetMethod("GetHashCode"));
            il.Emit(OpCodes.Ldtoken, type);
            il.Emit(OpCodes.Call, typeof(Identity).GetConstructor(BindingFlags.Instance | BindingFlags.Public, null, new[] { typeof(int), typeof(RuntimeTypeHandle) }, null));
            il.Emit(OpCodes.Ldarg_1);
            il.Emit(OpCodes.Ldloc_2);
            il.Emit(OpCodes.Ldloc_1);
            il.Emit(OpCodes.Callvirt, typeof(Dictionary<Identity, object>).GetMethod("Add"));
            il.Emit(OpCodes.Ldloc, i);
            il.Emit(OpCodes.Br, lb1);
            il.MarkLabel(lb2);
            il.Emit(OpCodes.Dup);
            il.Emit(OpCodes.Ldloc, i);
            il.Emit(OpCodes.Ldloc_0);
            il.Emit(OpCodes.Ldloc, i);
            il.Emit(OpCodes.Ldelem, arraytype);
            if (!arraytype.IsValueType && arraytype != typeof(String))
            {
                il.EmitCall(OpCodes.Call, typeof(ObjectCopy).GetMethod("CopyImpl1", BindingFlags.NonPublic | BindingFlags.Static).MakeGenericMethod(arraytype), null);
            }
            il.Emit(OpCodes.Stelem, arraytype);
            il.Emit(OpCodes.Ldloc, i);
            il.Emit(OpCodes.Ldc_I4_1);
            il.Emit(OpCodes.Sub);
            il.Emit(OpCodes.Dup);
            il.Emit(OpCodes.Stloc, i);
            il.MarkLabel(lb1);
            il.Emit(OpCodes.Ldc_I4_0);
            il.Emit(OpCodes.Clt);
            il.Emit(OpCodes.Brfalse, lb2);
        }
        il.Emit(OpCodes.Ret);

        return (Func<object, Dictionary<Identity, object>, object>)dm.CreateDelegate(typeof(Func<object, Dictionary<Identity, object>, object>));
    }
    
    static Action<object, Dictionary<Identity, object>, object> CreateCloneMethod2(Type type, Dictionary<Identity, object> objects)
    {
        Type tmptype;
        var fields = GetSettableFields(type);
        var dm = new DynamicMethod(string.Format("Copy{0}", Guid.NewGuid()), null, new[] { typeof(object), typeof(Dictionary<Identity, object>), typeof(object) }, true);
        var il = dm.GetILGenerator();
        il.DeclareLocal(type);
        il.DeclareLocal(type);
        il.DeclareLocal(typeof(Identity));
        if (!type.IsArray)
        {
            il.Emit(OpCodes.Ldarg_2);
            il.Emit(OpCodes.Castclass, type);
            il.Emit(OpCodes.Stloc_1);
            il.Emit(OpCodes.Ldarg_0);
            il.Emit(OpCodes.Castclass, type);
            il.Emit(OpCodes.Stloc_0);
            foreach (var field in fields)
            {
                if (!field.FieldType.IsValueType && field.FieldType != typeof(String))
                {
                    //不符合條件的字段,直接忽略,避免報錯。
                    if ((field.FieldType.IsArray && (field.FieldType.GetArrayRank() > 1 || (!(tmptype = field.FieldType.GetElementType()).IsValueType && tmptype != typeof(String) && tmptype.GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null) == null))) ||
                        (!field.FieldType.IsArray && field.FieldType.GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null) == null))
                        break;
                    il.Emit(OpCodes.Ldloc_1);
                    il.Emit(OpCodes.Ldloc_0);
                    il.Emit(OpCodes.Ldfld, field);
                    il.Emit(OpCodes.Ldarg_1);
                    il.EmitCall(OpCodes.Call, typeof(ObjectCopy).GetMethod("CopyImpl1", BindingFlags.NonPublic | BindingFlags.Static).MakeGenericMethod(field.FieldType), null);
                    il.Emit(OpCodes.Stfld, field);
                }
                else
                {
                    il.Emit(OpCodes.Ldloc_1);
                    il.Emit(OpCodes.Ldloc_0);
                    il.Emit(OpCodes.Ldfld, field);
                    il.Emit(OpCodes.Stfld, field);
                }
            }
            for (type = type.BaseType; type != null && type != typeof(object); type = type.BaseType)
            {
                //只需要查找基類的私有成員,共有或受保護的在派生類中直接被復制過了。
                fields = type.GetFields(BindingFlags.NonPublic | BindingFlags.Instance).ToList();
                foreach (var field in fields)
                {
                    if (!field.FieldType.IsValueType && field.FieldType != typeof(String))
                    {
                        //不符合條件的字段,直接忽略,避免報錯。
                        if ((field.FieldType.IsArray && (field.FieldType.GetArrayRank() > 1 || (!(tmptype = field.FieldType.GetElementType()).IsValueType && tmptype != typeof(String) && tmptype.GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null) == null))) ||
                            (!field.FieldType.IsArray && field.FieldType.GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null) == null))
                            break;
                        il.Emit(OpCodes.Ldloc_1);
                        il.Emit(OpCodes.Ldloc_0);
                        il.Emit(OpCodes.Ldfld, field);
                        il.Emit(OpCodes.Ldarg_1);
                        il.EmitCall(OpCodes.Call, typeof(ObjectCopy).GetMethod("CopyImpl1", BindingFlags.NonPublic | BindingFlags.Static).MakeGenericMethod(field.FieldType), null);
                        il.Emit(OpCodes.Stfld, field);
                    }
                    else
                    {
                        il.Emit(OpCodes.Ldloc_1);
                        il.Emit(OpCodes.Ldloc_0);
                        il.Emit(OpCodes.Ldfld, field);
                        il.Emit(OpCodes.Stfld, field);
                    }
                }
            }
        }
        else
        {
            Type arraytype = type.GetElementType();
            var i = il.DeclareLocal(typeof(int));
            var lb1 = il.DefineLabel();
            var lb2 = il.DefineLabel();
            il.Emit(OpCodes.Ldarg_0);
            il.Emit(OpCodes.Castclass, type);
            il.Emit(OpCodes.Dup);
            il.Emit(OpCodes.Stloc_0);
            il.Emit(OpCodes.Ldlen);
            il.Emit(OpCodes.Dup);
            il.Emit(OpCodes.Ldc_I4_1);
            il.Emit(OpCodes.Sub);
            il.Emit(OpCodes.Stloc, i);
            il.Emit(OpCodes.Ldarg_2);
            il.Emit(OpCodes.Castclass, type);
            il.Emit(OpCodes.Dup);
            il.Emit(OpCodes.Stloc_1);
            il.Emit(OpCodes.Ldloc, i);
            il.Emit(OpCodes.Br, lb1);
            il.MarkLabel(lb2);
            il.Emit(OpCodes.Dup);
            il.Emit(OpCodes.Ldloc, i);
            il.Emit(OpCodes.Ldloc_0);
            il.Emit(OpCodes.Ldloc, i);
            il.Emit(OpCodes.Ldelem, arraytype);
            if (!arraytype.IsValueType && arraytype != typeof(String))
            {
                il.EmitCall(OpCodes.Call, typeof(ObjectCopy).GetMethod("CopyImpl1", BindingFlags.NonPublic | BindingFlags.Static).MakeGenericMethod(arraytype), null);
            }
            il.Emit(OpCodes.Stelem, arraytype);
            il.Emit(OpCodes.Ldloc, i);
            il.Emit(OpCodes.Ldc_I4_1);
            il.Emit(OpCodes.Sub);
            il.Emit(OpCodes.Dup);
            il.Emit(OpCodes.Stloc, i);
            il.MarkLabel(lb1);
            il.Emit(OpCodes.Ldc_I4_0);
            il.Emit(OpCodes.Clt);
            il.Emit(OpCodes.Brfalse, lb2);
        }
        il.Emit(OpCodes.Ret);

        return (Action<object, Dictionary<Identity, object>, object>)dm.CreateDelegate(typeof(Action<object, Dictionary<Identity, object>, object>));
    }

    /// <summary>
    /// 對屬性進行復制,不查找父類,用於當前實體對象的屬性拷貝。
    /// </summary>
    /// <param name="type">實體類型</param>
    /// <param name="objects">復制鏈</param>
    /// <returns></returns>
    static Action<object, Dictionary<Identity, object>, object> CreateCloneMethod3(Type type, Dictionary<Identity, object> objects)
    {
        var props = GetPublicProps(type);
        var dm = new DynamicMethod(string.Format("Copy{0}", Guid.NewGuid()), null, new[] { typeof(object), typeof(Dictionary<Identity, object>), typeof(object) }, true);
        var il = dm.GetILGenerator();
        il.DeclareLocal(type);//存放源對象
        il.DeclareLocal(type);//存放目標對象
        il.DeclareLocal(typeof(Identity));//存放標識
        if (!type.IsArray)
        {
            il.Emit(OpCodes.Ldarg_2);
            il.Emit(OpCodes.Castclass, type);
            il.Emit(OpCodes.Stloc_1);//將參數中獲取的目標對象存放到局部變量1中
            il.Emit(OpCodes.Ldarg_0);
            il.Emit(OpCodes.Castclass, type);
            il.Emit(OpCodes.Stloc_0);//將參數中獲取的源對象存放到局部變量0中
            foreach (var prop in props)
            {
                if (!prop.Type.IsValueType && prop.Type != typeof(String))
                {
                    //不符合條件的屬性,直接忽略。
                    if (prop.Type.IsArray)
                        break;
                    il.Emit(OpCodes.Ldloc_0);
                    il.Emit(OpCodes.Call, prop.Getter);//獲取局部變量0中暫存的源對象的屬性
                    il.Emit(OpCodes.Ldarg_1);//加載參數中的復制鏈對象
                    il.Emit(OpCodes.Ldloc_1);
                    il.Emit(OpCodes.Call, prop.Getter);//獲取局部變量1中暫存的目標對象的屬性
                    il.EmitCall(OpCodes.Call, typeof(ObjectCopy).GetMethod("CopyImpl2", BindingFlags.NonPublic | BindingFlags.Static).MakeGenericMethod(prop.Type), null);
                        
                }
                else
                {
                    il.Emit(OpCodes.Ldloc_1);
                    il.Emit(OpCodes.Ldloc_0);
                    il.Emit(OpCodes.Call, prop.Getter);
                    il.Emit(OpCodes.Call, prop.Setter);
                }
            }                
        }
        il.Emit(OpCodes.Ret);

        return (Action<object, Dictionary<Identity, object>, object>)dm.CreateDelegate(typeof(Action<object, Dictionary<Identity, object>, object>));
    }

    static T CopyImpl1<T>(T source, Dictionary<Identity, object> objects) where T : class
    {
        //為空則直接返回null
        if (source == null)
            return null;

        Type type = source.GetType();
        Identity id = new Identity(source.GetHashCode(), type.TypeHandle);
        object result;
        //如果發現曾經復制過,用之前的,從而停止遞歸復制。
        if (!objects.TryGetValue(id, out result))
        {
            //最后查找對象的復制方法,如果不存在,創建新的。
            Func<object, Dictionary<Identity, object>, object> method;
            if (!methods1.TryGetValue(type, out method))
            {
                method = CreateCloneMethod1(type, objects);
                methods1.Add(type, method);
            }
            result = method(source, objects);
        }
        return (T)result;
    }

    static void CopyImpl2<T>(T source, T target, Dictionary<Identity, object> objects) where T : class
    {
        Type type = typeof(T);
        Identity id = new Identity(source.GetHashCode(), type.TypeHandle);
        object result;
        //如果發現曾經復制過,用之前的,從而停止遞歸復制。
        if (!objects.TryGetValue(id, out result))
        {
            objects.Add(new Identity(source.GetHashCode(), type.TypeHandle), source);
            //最后查找對象的復制方法,如果不存在,創建新的。
            Action<object, Dictionary<Identity, object>, object> method;
            if (!methods3.TryGetValue(type, out method))
            {
                method = CreateCloneMethod3(type, objects);
                methods3.Add(type, method);
            }
            method(source, objects, target);
        }
    }


    /// <summary>
    /// 創建對象深度復制的副本
    /// </summary>
    public static T ToObjectCopy<T>(this T source) where T : class

    {
        Type type = source.GetType();
        Dictionary<Identity, object> objects = new Dictionary<Identity, object>();//存放內嵌引用類型的復制鏈,避免構成一個環。
        Func<object, Dictionary<Identity, object>, object> method;
        if (!methods1.TryGetValue(type, out method))
        {
            method = CreateCloneMethod1(type, objects);
            methods1.Add(type, method);
        }
        return (T)method(source, objects);
    }


    /// <summary>
    /// 將source對象的所有屬性復制到target對象中,深度復制
    /// </summary>
    public static void ObjectCopyTo<T>(this T source, T target) where T : class
    {
        if (target == null)
            throw new Exception("將要復制的目標未初始化");
        Type type = source.GetType();
        if (type != target.GetType())
            throw new Exception("要復制的對象類型不同,無法復制");
        Dictionary<Identity, object> objects = new Dictionary<Identity, object>();//存放內嵌引用類型的復制鏈,避免構成一個環。
        objects.Add(new Identity(source.GetHashCode(), type.TypeHandle), source);
        Action<object, Dictionary<Identity, object>, object> method;
        if (!methods2.TryGetValue(type, out method))
        {
            method = CreateCloneMethod2(type, objects);
            methods2.Add(type, method);
        }
        method(source, objects, target);
    }

    /// <summary>
    /// 將source對象的所有屬性復制到target對象中,僅對公有屬性復制,不查找父類,不創建引用屬性,但復制引用屬性內部的屬性。
    /// </summary>
    public static void PropCopyTo<T>(this T source, T target) where T : class
    {
        if (target == null)
            throw new Exception("將要復制的目標未初始化");
        Type type = typeof(T);
        Dictionary<Identity, object> objects = new Dictionary<Identity, object>();//存放內嵌引用類型的復制鏈,避免構成一個環。
        objects.Add(new Identity(source.GetHashCode(), type.TypeHandle), source);
        Action<object, Dictionary<Identity, object>, object> method;
        if (!methods3.TryGetValue(type, out method))
        {
            method = CreateCloneMethod3(type, objects);
            methods3.Add(type, method);
        }
        method(source, objects, target);
    }
}

代碼中,ToObjectCopy是為了創建一個新的實體對象,ObjectCopyTo是為了將實體對象深度復制到另一個實體對象上面,PropCopyTo則是為了將對象屬性淺拷貝另一個對象中。

一、為什么要深度復制實體?
如果實體類中某個屬性是byte[]類型的,一般的賦值是傳遞地址,顯然不深度拷貝就不能完成類似引用類型屬性的真正拷貝。

二、如果實體類中某個實體的屬性是另一個實體類,是否會被復制?
會,因此設計實體類的時候,不要將引用關系直接設計到類的屬性中,雖然可以清晰的訪問父子關系,但是實體類在創建時難度就會增加,不利於自動化編程,實體就應該如實反映數據庫的表結構。

三、為什么要屬性的淺拷貝?
因為對象的深度拷貝是通過字段的直接賦值實現的,那樣效率最高,但是跳過了屬性訪問器,無法觸發屬性變化的事件,而對於實體類集合特別是ObservableCollection<T>需要通知UI刷新,需要觸發屬性變化的事件,一些數據庫自動更新也是通過屬性改變事件來觸發的。

四、什么時候要復制實體?
當我們需要一個對實體修改的臨時副本,不直接對數據源操作的時候,就需要用到它,這樣可以提供一個取消操作的可能——放棄被修改的實體對象即可。

曾在CSDN上發布過一個類似版本,這是改進版,都是本人原創。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM