一、情景分析
对象克隆对程序开发来说是一个很常见的小需求,就c#而言实现也很简单,浅克隆的的话使用MemberwiseClone,深克隆的话使用一种序列化方式,然后在反序列化就可以了。这篇文章提供的代码,不涉及到高深的技术,如果有什么亮点的话,可能就是通用性强点。
二、代码设计要求
1、要实现两个方法:浅克隆Clone和深DeepClone
2、要做到比较通用,支持泛型。使用方便,一遍需求不用增加代码
3、根据约定优于配置的原则,在设计中减少接口和配置文件的约束。但要能灵活的控制克隆过程,尤其是深克隆
三、代码实现
/// <summary>
/// 对象克隆工具
/// </summary>
public static class ObjectCloneUtils
{
static ObjectCloneUtils()
{
InitMemberwiseClone();
}
#region 克隆缓存
/// <summary>
/// 浅克隆
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="obj"></param>
/// <returns></returns>
public static T Clone<T>(T obj)
{
return (T)m_MemberwiseClone?.Invoke(obj);
}
private static Func<object, object> m_MemberwiseClone;
/// <summary>
/// 初始化浅克隆方法
/// </summary>
private static void InitMemberwiseClone()
{
var type = typeof(Object);
var memberwiseClone = type.GetMethod("MemberwiseClone",
BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);
var source = Expression.Parameter(typeof(Object), "p");
var body_objToType = Expression.Convert(source, memberwiseClone.DeclaringType);
MethodCallExpression cloneMethod = Expression.Call(body_objToType, memberwiseClone);
var useValue = Expression.Convert(cloneMethod, typeof(object));
m_MemberwiseClone= Expression.Lambda<Func<object, object>>(useValue, source).Compile();
}
#endregion
#region 深克隆
/// <summary>
/// 深克隆
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="obj"></param>
/// <param name="useDefaultClone"></param>
/// <returns></returns>
public static T DeepClone<T>(T obj, bool useDefaultClone = true)
{
var method = GetDeepCloneMethod(obj.GetType());
if (method != null)
{
return (T)method?.Invoke(obj);
}
else if (useDefaultClone)
{
return (T)DeepCloneInner(obj);
}
return default(T);
}
/// <summary>
/// 内部深克隆方法
/// </summary>
/// <param name="obj"></param>
/// <returns></returns>
private static object DeepCloneInner(object obj)
{
MemoryStream stream = new MemoryStream();
BinaryFormatter formatter = new BinaryFormatter();
formatter.Serialize(stream, obj);
stream.Position = 0;
return formatter.Deserialize(stream);
}
private static Dictionary<Type, Func<object, object>> m_DeepCloneMap = new Dictionary<Type, Func<object, object>>();
/// <summary>
/// 获取深克隆方法
/// </summary>
/// <param name="type"></param>
/// <returns></returns>
private static Func<object, object> GetDeepCloneMethod(Type type)
{
var flag = m_DeepCloneMap.TryGetValue(type, out Func<object, object> method);
if (!flag)
{
var deepClone = type.GetMethod("DeepClone", BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);
if (deepClone != null)
{
var source = Expression.Parameter(typeof(Object), "p");
var body_objToType = Expression.Convert(source, deepClone.DeclaringType);
MethodCallExpression cloneMethod = Expression.Call(body_objToType, deepClone);
var useValue = Expression.Convert(cloneMethod, typeof(object));
var deep = Expression.Lambda<Func<object, object>>(useValue, source).Compile();
m_DeepCloneMap[type] = deep;
return deep;
}
else
{
//如果没有DeepClone直接赋值null,为了下一次查询时容易读到结果
m_DeepCloneMap[type] = null;
return null;
}
}
return method;
}
#endregion
}
四、代码解析
如果您只需要一些可用的代码,上面的代码拷贝走就能用了。如果您还想听我瞎咧咧两句,那就继续看看下面的分析
1、浅克隆部分
浅克隆用到了Object对象的MemberwiseClone方法,这个方法在非静态类里面直接调用很容易,这里有一个小难点就是要在对象外边调用这个受保护的方法,这里我有一点想告诉c#初学者,无论对象的成员是公有的,私有的还是受保护的,用反射都能取到。取到MemberwiseClone之后,缓存起来直接调用就可以了,但是反射调用方法速度不是很理想,我这里用lamda表达式将反射方法转换成了一个委托,这一步可以加快方法速度。当然速度不可能快过你在非静态类中直接调用该方法。
深克隆,用到了一种约定的方式,而不是使用接口。约定只要类中实现了名为DeepClone的方法,当该工具类调用深克隆时,会使用实例的DeepClone方法。如果对象没有实现DeepClone方法,工具内部会使用默认的深度克隆方法对对象进行克隆。
内部的深度克隆方法是使用二进制序列化的方式实现的,当然如果想使用二进制序列化的类,需要给类加入可序列化标记 [Serializable]