取消

Emit克隆和反射克隆性能对比

如果少量使用(<100次)或对性能不敏感可以直接用反射,否则使用IL方式


测试代码

  1. 要克隆的类型含有200个属性,100个int,100个string
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
public class Person
{
    #region Name
    public int Age0 { get; set; } = 0;
    public int Age1 { get; set; } = 1;
    public int Age2 { get; set; } = 2;
    public int Age3 { get; set; } = 3;
    public int Age4 { get; set; } = 4;
    public int Age5 { get; set; } = 5;
    public int Age6 { get; set; } = 6;
    public int Age7 { get; set; } = 7;
    public int Age8 { get; set; } = 8;
    public int Age9 { get; set; } = 9;
    public int Age10 { get; set; } = 10;
    public int Age11 { get; set; } = 11;
    public int Age12 { get; set; } = 12;
    public int Age13 { get; set; } = 13;
    public int Age14 { get; set; } = 14;
    public int Age15 { get; set; } = 15;
    public int Age16 { get; set; } = 16;
    public int Age17 { get; set; } = 17;
    public int Age18 { get; set; } = 18;
    public int Age19 { get; set; } = 19;
    public int Age20 { get; set; } = 20;
    public int Age21 { get; set; } = 21;
    public int Age22 { get; set; } = 22;
    public int Age23 { get; set; } = 23;
    public int Age24 { get; set; } = 24;
    public int Age25 { get; set; } = 25;
    public int Age26 { get; set; } = 26;
    public int Age27 { get; set; } = 27;
    public int Age28 { get; set; } = 28;
    public int Age29 { get; set; } = 29;
    public int Age30 { get; set; } = 30;
    public int Age31 { get; set; } = 31;
    public int Age32 { get; set; } = 32;
    public int Age33 { get; set; } = 33;
    public int Age34 { get; set; } = 34;
    public int Age35 { get; set; } = 35;
    public int Age36 { get; set; } = 36;
    public int Age37 { get; set; } = 37;
    public int Age38 { get; set; } = 38;
    public int Age39 { get; set; } = 39;
    public int Age40 { get; set; } = 40;
    public int Age41 { get; set; } = 41;
    public int Age42 { get; set; } = 42;
    public int Age43 { get; set; } = 43;
    public int Age44 { get; set; } = 44;
    public int Age45 { get; set; } = 45;
    public int Age46 { get; set; } = 46;
    public int Age47 { get; set; } = 47;
    public int Age48 { get; set; } = 48;
    public int Age49 { get; set; } = 49;
    public int Age50 { get; set; } = 50;
    public int Age51 { get; set; } = 51;
    public int Age52 { get; set; } = 52;
    public int Age53 { get; set; } = 53;
    public int Age54 { get; set; } = 54;
    public int Age55 { get; set; } = 55;
    public int Age56 { get; set; } = 56;
    public int Age57 { get; set; } = 57;
    public int Age58 { get; set; } = 58;
    public int Age59 { get; set; } = 59;
    public int Age60 { get; set; } = 60;
    public int Age61 { get; set; } = 61;
    public int Age62 { get; set; } = 62;
    public int Age63 { get; set; } = 63;
    public int Age64 { get; set; } = 64;
    public int Age65 { get; set; } = 65;
    public int Age66 { get; set; } = 66;
    public int Age67 { get; set; } = 67;
    public int Age68 { get; set; } = 68;
    public int Age69 { get; set; } = 69;
    public int Age70 { get; set; } = 70;
    public int Age71 { get; set; } = 71;
    public int Age72 { get; set; } = 72;
    public int Age73 { get; set; } = 73;
    public int Age74 { get; set; } = 74;
    public int Age75 { get; set; } = 75;
    public int Age76 { get; set; } = 76;
    public int Age77 { get; set; } = 77;
    public int Age78 { get; set; } = 78;
    public int Age79 { get; set; } = 79;
    public int Age80 { get; set; } = 80;
    public int Age81 { get; set; } = 81;
    public int Age82 { get; set; } = 82;
    public int Age83 { get; set; } = 83;
    public int Age84 { get; set; } = 84;
    public int Age85 { get; set; } = 85;
    public int Age86 { get; set; } = 86;
    public int Age87 { get; set; } = 87;
    public int Age88 { get; set; } = 88;
    public int Age89 { get; set; } = 89;
    public int Age90 { get; set; } = 90;
    public int Age91 { get; set; } = 91;
    public int Age92 { get; set; } = 92;
    public int Age93 { get; set; } = 93;
    public int Age94 { get; set; } = 94;
    public int Age95 { get; set; } = 95;
    public int Age96 { get; set; } = 96;
    public int Age97 { get; set; } = 97;
    public int Age98 { get; set; } = 98;
    public int Age99 { get; set; } = 99;
    public string Name0 { get; set; } = "name0";
    public string Name1 { get; set; } = "name1";
    public string Name2 { get; set; } = "name2";
    public string Name3 { get; set; } = "name3";
    public string Name4 { get; set; } = "name4";
    public string Name5 { get; set; } = "name5";
    public string Name6 { get; set; } = "name6";
    public string Name7 { get; set; } = "name7";
    public string Name8 { get; set; } = "name8";
    public string Name9 { get; set; } = "name9";
    public string Name10 { get; set; } = "name10";
    public string Name11 { get; set; } = "name11";
    public string Name12 { get; set; } = "name12";
    public string Name13 { get; set; } = "name13";
    public string Name14 { get; set; } = "name14";
    public string Name15 { get; set; } = "name15";
    public string Name16 { get; set; } = "name16";
    public string Name17 { get; set; } = "name17";
    public string Name18 { get; set; } = "name18";
    public string Name19 { get; set; } = "name19";
    public string Name20 { get; set; } = "name20";
    public string Name21 { get; set; } = "name21";
    public string Name22 { get; set; } = "name22";
    public string Name23 { get; set; } = "name23";
    public string Name24 { get; set; } = "name24";
    public string Name25 { get; set; } = "name25";
    public string Name26 { get; set; } = "name26";
    public string Name27 { get; set; } = "name27";
    public string Name28 { get; set; } = "name28";
    public string Name29 { get; set; } = "name29";
    public string Name30 { get; set; } = "name30";
    public string Name31 { get; set; } = "name31";
    public string Name32 { get; set; } = "name32";
    public string Name33 { get; set; } = "name33";
    public string Name34 { get; set; } = "name34";
    public string Name35 { get; set; } = "name35";
    public string Name36 { get; set; } = "name36";
    public string Name37 { get; set; } = "name37";
    public string Name38 { get; set; } = "name38";
    public string Name39 { get; set; } = "name39";
    public string Name40 { get; set; } = "name40";
    public string Name41 { get; set; } = "name41";
    public string Name42 { get; set; } = "name42";
    public string Name43 { get; set; } = "name43";
    public string Name44 { get; set; } = "name44";
    public string Name45 { get; set; } = "name45";
    public string Name46 { get; set; } = "name46";
    public string Name47 { get; set; } = "name47";
    public string Name48 { get; set; } = "name48";
    public string Name49 { get; set; } = "name49";
    public string Name50 { get; set; } = "name50";
    public string Name51 { get; set; } = "name51";
    public string Name52 { get; set; } = "name52";
    public string Name53 { get; set; } = "name53";
    public string Name54 { get; set; } = "name54";
    public string Name55 { get; set; } = "name55";
    public string Name56 { get; set; } = "name56";
    public string Name57 { get; set; } = "name57";
    public string Name58 { get; set; } = "name58";
    public string Name59 { get; set; } = "name59";
    public string Name60 { get; set; } = "name60";
    public string Name61 { get; set; } = "name61";
    public string Name62 { get; set; } = "name62";
    public string Name63 { get; set; } = "name63";
    public string Name64 { get; set; } = "name64";
    public string Name65 { get; set; } = "name65";
    public string Name66 { get; set; } = "name66";
    public string Name67 { get; set; } = "name67";
    public string Name68 { get; set; } = "name68";
    public string Name69 { get; set; } = "name69";
    public string Name70 { get; set; } = "name70";
    public string Name71 { get; set; } = "name71";
    public string Name72 { get; set; } = "name72";
    public string Name73 { get; set; } = "name73";
    public string Name74 { get; set; } = "name74";
    public string Name75 { get; set; } = "name75";
    public string Name76 { get; set; } = "name76";
    public string Name77 { get; set; } = "name77";
    public string Name78 { get; set; } = "name78";
    public string Name79 { get; set; } = "name79";
    public string Name80 { get; set; } = "name80";
    public string Name81 { get; set; } = "name81";
    public string Name82 { get; set; } = "name82";
    public string Name83 { get; set; } = "name83";
    public string Name84 { get; set; } = "name84";
    public string Name85 { get; set; } = "name85";
    public string Name86 { get; set; } = "name86";
    public string Name87 { get; set; } = "name87";
    public string Name88 { get; set; } = "name88";
    public string Name89 { get; set; } = "name89";
    public string Name90 { get; set; } = "name90";
    public string Name91 { get; set; } = "name91";
    public string Name92 { get; set; } = "name92";
    public string Name93 { get; set; } = "name93";
    public string Name94 { get; set; } = "name94";
    public string Name95 { get; set; } = "name95";
    public string Name96 { get; set; } = "name96";
    public string Name97 { get; set; } = "name97";
    public string Name98 { get; set; } = "name98";
    public string Name99 { get; set; } = "name99";
    #endregion
}
  1. 测试代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
static void Main(string[] args)
{
    var length = 100;
    var sw = Stopwatch.StartNew();
    var person = new Person();
    var person2 = new Person();
    for (int i = 0; i < length; i++)
    {
            Clone.CloneObjectWithIL(person, person2);
    }
    sw.Stop();
    Console.WriteLine("IL:" + sw.ElapsedMilliseconds);


    sw.Restart();
    for (int i = 0; i < length; i++)
    {
        Clone.CloneWithReflection(person, person2);
    }
    sw.Stop();
    Console.WriteLine("Ref:" + sw.ElapsedMilliseconds);
}

克隆实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
/// <summary>
/// 提供快速的对象深复制
/// </summary>
public static class Clone
{
    /// <summary>
    /// 提供使用 IL 的方式快速对象深复制
    /// 要求本方法具有T可访问
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="source">源</param>
    /// <param name="los">从源复制属性</param>
    /// <exception cref="MethodAccessException">如果输入的T没有本方法可以访问,那么就会出现这个异常</exception>
    // ReSharper disable once InconsistentNaming
    public static void CloneObjectWithIL<T>(T source, T los)
    {
        CloneObjectWithIL(source, los, new string[] { });
    }
    public static void CloneObjectWithIL<T>(T source, T los, string[] ignorePropertyNames)
    {
        //参见 http://lindexi.oschina.io/lindexi/post/C-%E4%BD%BF%E7%94%A8Emit%E6%B7%B1%E5%85%8B%E9%9A%86/
        if (CachedIl.ContainsKey(typeof(T)))
        {
            ((Action<T, T>)CachedIl[typeof(T)])(source, los);
            return;
        }
        var dynamicMethod = new DynamicMethod("Clone", null, new[] { typeof(T), typeof(T) });
        ILGenerator generator = dynamicMethod.GetILGenerator();

        foreach (var temp in typeof(T)
            .GetProperties(BindingFlags.Instance | BindingFlags.Public)
            .Where(temp => temp.CanRead && temp.CanWrite))
        {
            //不复制静态类属性
            if (temp.GetAccessors(true)[0].IsStatic || ignorePropertyNames.Contains(temp.Name))
            {
                continue;
            }

            generator.Emit(OpCodes.Ldarg_1);// los
            generator.Emit(OpCodes.Ldarg_0);// s
            generator.Emit(OpCodes.Callvirt, temp.GetMethod);
            generator.Emit(OpCodes.Callvirt, temp.SetMethod);
        }
        generator.Emit(OpCodes.Ret);
        var clone = (Action<T, T>)dynamicMethod.CreateDelegate(typeof(Action<T, T>));
        CachedIl[typeof(T)] = clone;
        clone(source, los);
    }
    public static T CloneObjectWithIL<T>(T myObject)
    {
        Delegate myExec;
        if (!CachedIl.TryGetValue(typeof(T), out myExec))
        {
            // Create ILGenerator
            DynamicMethod dymMethod = new DynamicMethod("DoClone", typeof(T), new Type[] { typeof(T) }, true);
            ConstructorInfo cInfo = myObject.GetType().GetConstructor(new Type[] { });

            ILGenerator generator = dymMethod.GetILGenerator();

            LocalBuilder lbf = generator.DeclareLocal(typeof(T));
            //lbf.SetLocalSymInfo("_temp");

            generator.Emit(OpCodes.Newobj, cInfo);
            generator.Emit(OpCodes.Stloc_0);
            foreach (FieldInfo field in myObject.GetType().GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))
            {
                // Load the new object on the eval stack... (currently 1 item on eval stack)
                generator.Emit(OpCodes.Ldloc_0);
                // Load initial object (parameter)          (currently 2 items on eval stack)
                generator.Emit(OpCodes.Ldarg_0);
                // Replace value by field value             (still currently 2 items on eval stack)
                generator.Emit(OpCodes.Ldfld, field);
                // Store the value of the top on the eval stack into the object underneath that value on the value stack.
                //  (0 items on eval stack)
                generator.Emit(OpCodes.Stfld, field);
            }

            // Load new constructed obj on eval stack -> 1 item on stack
            generator.Emit(OpCodes.Ldloc_0);
            // Return constructed object.   --> 0 items on stack
            generator.Emit(OpCodes.Ret);

            myExec = dymMethod.CreateDelegate(typeof(Func<T, T>));
            CachedIl.Add(typeof(T), myExec);
        }
        return ((Func<T, T>)myExec)(myObject);
    }
    private static Dictionary<Type, Delegate> CachedIl { set; get; } = new Dictionary<Type, Delegate>();
    public static void CloneWithReflection<T, E>(this T source, E los, params string[] methods)
    {
        var listMethods = new List<string>();
        listMethods.AddRange(methods);

        if (source == null || los == null)
        {
            throw new Exception(" iLIS.Common Clone扩展方法 参数克隆者与被克隆者参数不能为Null类型调用对象" + typeof(T).FullName + "参数对象" + typeof(E).FullName);

        }
        var tps = typeof(T).GetProperties();
        var eps = typeof(E).GetProperties();
        foreach (var tp in tps)
        {

            if (listMethods != null && listMethods.Contains(tp.Name))
            {
                continue;
            }
            var ep = eps.FirstOrDefault(a => a.Name == tp.Name);
            if (ep != null)
            {
                var value = ep.GetValue(los, null);

                if (TypeHelper.GetTargetType(ep.PropertyType).FullName != TypeHelper.GetTargetType(tp.PropertyType).FullName)
                {
                    throw new Exception("需要复制对象" + typeof(T).FullName + "的" + tp.Name + "的属性与数据源对象" + typeof(E).FullName + "的" + ep.Name + "属性的类型不一样");
                }

                if (value != null && tp.CanWrite)
                {
                    tp.SetValue(source, value, null);
                }
            }

        }
    }
}
public class TypeHelper
{

    public static object ChangeType(string str, Type basicType)
    {
        if (basicType == typeof(string)) return str;
        if (string.IsNullOrEmpty(str)) return GetBasicTypeDefaultValue(basicType);

        var targetType = GetTargetType(basicType);
        if (targetType == typeof(Guid))
        {
            return Guid.Parse(str);
        }
        if (targetType.IsEnum)
        {
            return Enum.Parse(targetType, str);
        }
        return Convert.ChangeType(str, targetType);
    }
    public static object GetBasicTypeDefaultValue(Type basicType)
    {
        if (IsNullableType(basicType)) return null;
        if (basicType == typeof(int)
            || basicType == typeof(uint)
            || basicType == typeof(byte)
            || basicType == typeof(sbyte)
            || basicType == typeof(long)
            || basicType == typeof(ulong)
            || basicType == typeof(float)
            || basicType == typeof(double)
            || basicType == typeof(decimal)
            )
        {
            return 0;
        }
        else if (basicType == typeof(bool))
        {
            return false;
        }
        else if (basicType == typeof(DateTime?))
        {
            return null;
        }
        throw new NotSupportedException(string.Format("无法获取类型{0}的默认值", basicType.FullName));
    }

    public static Type GetTargetType(Type basicType)
    {
        var targetType = basicType;
        if (IsNullableType(basicType))
        {
            targetType = basicType.GetGenericArguments()[0];
        }
        return targetType;
    }
    /// <summary>
    /// 是否为可空类型。如:int?
    /// </summary>
    public static bool IsNullableType(Type theType)
    {
        return (theType.IsGenericType && theType.GetGenericTypeDefinition() == typeof(Nullable<>));
    }
}

参考资料

本文会经常更新,请阅读原文: https://dashenxian.github.io/post/Emit%E5%85%8B%E9%9A%86%E5%92%8C%E5%8F%8D%E5%B0%84%E5%85%8B%E9%9A%86%E6%80%A7%E8%83%BD%E5%AF%B9%E6%AF%94 ,以避免陈旧错误知识的误导,同时有更好的阅读体验。

知识共享许可协议

本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。欢迎转载、使用、重新发布,但务必保留文章署名 小神仙 (包含链接: https://dashenxian.github.io ),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。如有任何疑问,请 与我联系 (125880321@qq.com)

登录 GitHub 账号进行评论