1. MemberwiseClone 2. 逐个复制每个字段(手动)
我发现(2)比(1)更快。我想知道是否还有其他方法可以进行浅层复制?
这是一个复杂的主题,有许多可能的解决方案和各种利弊。这里有一篇精彩的文章(链接),概述了C#中几种不同的复制方式。简要总结如下:
手动克隆
繁琐,但控制程度高。
使用MemberwiseClone克隆
只创建浅层复制,即对于引用类型字段,原始对象和其副本引用相同的对象。
使用Reflection(反射)克隆
默认情况下为浅层复制,可以重写为深层复制。优点:自动化;缺点:反射速度较慢。
使用Serialization(序列化)克隆
简单易行,自动化。放弃了一些控制,而且序列化是所有方法中最慢的。
使用IL或Extension Methods克隆
更高级的解决方案,不太常见。
MemberwiseClone
会执行一些额外的检查和操作,因此对于字段较少的对象,它可能实际上会稍微慢一些。但是,在任何情况下,您只有在面临性能问题时才需要关注这一点。不要根据“直觉”进行优化,而应该进行分析。如果你需要频繁地克隆对象以至于 MemberwiseClone
影响了性能,那么你可能应该使用结构体而不是类。或者也许需要更彻底的架构变更 :D - Luaan我想先引用几句话:
实际上,对于复杂类型,MemberwiseClone通常比其他方法更好。
以及
我很困惑。对于浅拷贝,MemberwiseClone()应该比其他任何方法都要快。[...]
理论上,最好的浅拷贝实现是C++的拷贝构造函数:它在编译时就知道大小,然后对所有字段进行成员逐一克隆。其次是使用memcpy
或类似的东西,这基本上就是MemberwiseClone
应该的工作方式。这意味着,理论上它应该在性能方面超越所有其他可能性。对吧?
…但显然它并不是非常快,也不能消灭所有其他解决方案。在底部,我实际上发布了一个速度超过2倍的解决方案。所以:错了。
测试MemberwiseClone内部
让我们使用一个简单的可平直类型进行一些测试,以检查这里关于性能的基本假设:
[StructLayout(LayoutKind.Sequential)]
public class ShallowCloneTest
{
public int Foo;
public long Bar;
public ShallowCloneTest Clone()
{
return (ShallowCloneTest)base.MemberwiseClone();
}
}
MemberwiseClone
和原始的memcpy
的性能来检查其性能,这是因为它是可平坦化类型。ShallowCloneTest t1 = new ShallowCloneTest() { Bar = 1, Foo = 2 };
Stopwatch sw = Stopwatch.StartNew();
int total = 0;
for (int i = 0; i < 10000000; ++i)
{
var cloned = t1.Clone(); // 0.40s
total += cloned.Foo;
}
Console.WriteLine("Took {0:0.00}s", sw.Elapsed.TotalSeconds);
sw = Stopwatch.StartNew();
total = 0;
uint bytes = (uint)Marshal.SizeOf(t1.GetType());
GCHandle handle1 = GCHandle.Alloc(t1, GCHandleType.Pinned);
IntPtr ptr1 = handle1.AddrOfPinnedObject();
for (int i = 0; i < 10000000; ++i)
{
ShallowCloneTest t2 = new ShallowCloneTest(); // 0.03s
GCHandle handle2 = GCHandle.Alloc(t2, GCHandleType.Pinned); // 0.75s (+ 'Free' call)
IntPtr ptr2 = handle2.AddrOfPinnedObject(); // 0.06s
memcpy(ptr2, ptr1, new UIntPtr(bytes)); // 0.17s
handle2.Free();
total += t2.Foo;
}
handle1.Free();
Console.WriteLine("Took {0:0.00}s", sw.Elapsed.TotalSeconds);
MemberwiseClone
会使用方法1,这意味着由于固定过程,您将遭受性能损失。memcpy
更快一样)。不需要固定数据意味着我们不会受到额外的性能损失。由于.NET JIT转换为汇编语言,理论上这意味着我们应该能够使用简单的IL发射来进行更快的实现,并允许编译器进行优化。memcpy
或更好的性能:0.17秒。call
、创建对象和执行一堆copy
指令。它看起来有点像上面的Cloner
实现,但是有一些重要的区别(最显著的是没有Dictionary
和冗余的CreateDelegate
调用)。以下是代码:public static class Cloner<T>
{
private static Func<T, T> cloner = CreateCloner();
private static Func<T, T> CreateCloner()
{
var cloneMethod = new DynamicMethod("CloneImplementation", typeof(T), new Type[] { typeof(T) }, true);
var defaultCtor = typeof(T).GetConstructor(new Type[] { });
var generator = cloneMethod .GetILGenerator();
var loc1 = generator.DeclareLocal(typeof(T));
generator.Emit(OpCodes.Newobj, defaultCtor);
generator.Emit(OpCodes.Stloc, loc1);
foreach (var field in typeof(T).GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))
{
generator.Emit(OpCodes.Ldloc, loc1);
generator.Emit(OpCodes.Ldarg_0);
generator.Emit(OpCodes.Ldfld, field);
generator.Emit(OpCodes.Stfld, field);
}
generator.Emit(OpCodes.Ldloc, loc1);
generator.Emit(OpCodes.Ret);
return ((Func<T, T>)cloneMethod.CreateDelegate(typeof(Func<T, T>)));
}
public static T Clone(T myObject)
{
return cloner(myObject);
}
}
MemberwiseClone
快2.5倍。memcpy
持平,后者在正常情况下是“最优解”。GetConstructor
返回null。在这种情况下,请使用其他GetConstructor
签名之一来获取受保护或私有构造函数。
请参阅https://learn.microsoft.com/en-us/dotnet/api/system.type.getconstructor?view=netframework-4.8。Cloner<BaseClass>.Clone(someDerivedObject);
吗?我猜它会失败,因为你正在使用静态编译时字段信息来创建克隆器。克隆实现通常在基类中定义(例如 MemberwiseClone
),它不可能知道未来的派生类,但仍必须将所有字段复制到层次结构中。 - Bruce Piersonthis.GetType()
。就个人而言,我会尽量避免在基类中添加此类实用程序函数。 - atlaste我感到困惑。MemberwiseClone()
应该对于浅拷贝的性能比其他任何方法都要好。在CLI中,除了RCW之外的任何类型都可以通过以下序列进行浅拷贝:
memcpy
将原始数据从原始位置复制到新的位置。由于目标对象在nursery中,因此不需要写入屏障。SuppressFinalize
并且在对象标题中存储了这样的标志,请在克隆中取消设置它。CLR内部团队的某个人能解释一下为什么情况不是这样吗?
MemberwiseClone
是一个外部调用,这意味着它需要固定内存。固定内存是一个相对比较慢的过程。我在下面的回答中发布了我的测试细节和结果。我现在没有原生调用基准数据了,但我相当确信固定内存/本机互操作和额外的虚拟表调用(clone、sizeof)占据了你发现的memcpy的开销。 - atlaste为什么要把事情复杂化呢?MemberwiseClone就足够了。
public class ClassA : ICloneable
{
public object Clone()
{
return this.MemberwiseClone();
}
}
// let's say you want to copy the value (not reference) of the member of that class.
public class Main()
{
ClassA myClassB = new ClassA();
ClassA myClassC = new ClassA();
myClassB = (ClassA) myClassC.Clone();
}
public static class Cloner
{
static Dictionary<Type, Delegate> _cachedIL = new Dictionary<Type, Delegate>();
public static T Clone<T>(T myObject)
{
Delegate myExec = null;
if (!_cachedIL.TryGetValue(typeof(T), out myExec))
{
var dymMethod = new DynamicMethod("DoClone", typeof(T), new Type[] { typeof(T) }, true);
var cInfo = myObject.GetType().GetConstructor(new Type[] { });
var generator = dymMethod.GetILGenerator();
var lbf = generator.DeclareLocal(typeof(T));
generator.Emit(OpCodes.Newobj, cInfo);
generator.Emit(OpCodes.Stloc_0);
foreach (var 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);
}
}
namespace MoeCard.TestConsole
{
class Program
{
static void Main(string[] args)
{
Program p = new Program() { AAA = Guid.NewGuid().ToString(), BBB = 123 };
Stopwatch sw = Stopwatch.StartNew();
for (int i = 0; i < 10000; i++)
{
p.Copy1();
}
sw.Stop();
Console.WriteLine("Manual Copy:" + sw.Elapsed);
sw.Restart();
for (int i = 0; i < 10000; i++)
{
p.Copy2();
}
sw.Stop();
Console.WriteLine("MemberwiseClone:" + sw.Elapsed);
Console.ReadLine();
}
public string AAA;
public int BBB;
public Class1 CCC = new Class1();
public Program Copy1()
{
return new Program() { AAA = AAA, BBB = BBB, CCC = CCC };
}
public Program Copy2()
{
return this.MemberwiseClone() as Program;
}
public class Class1
{
public DateTime Date = DateTime.Now;
}
}
}
#region 数据克隆
/// <summary>
/// 依据不同类型所存储的克隆句柄集合
/// </summary>
private static readonly Dictionary<Type, Func<object, object>> CloneHandlers = new Dictionary<Type, Func<object, object>>();
/// <summary>
/// 根据指定的实例,克隆一份新的实例
/// </summary>
/// <param name="source">待克隆的实例</param>
/// <returns>被克隆的新的实例</returns>
public static object CloneInstance(object source)
{
if (source == null)
{
return null;
}
Func<object, object> handler = TryGetOrAdd(CloneHandlers, source.GetType(), CreateCloneHandler);
return handler(source);
}
/// <summary>
/// 根据指定的类型,创建对应的克隆句柄
/// </summary>
/// <param name="type">数据类型</param>
/// <returns>数据克隆句柄</returns>
private static Func<object, object> CreateCloneHandler(Type type)
{
return Delegate.CreateDelegate(typeof(Func<object, object>), new Func<object, object>(CloneAs<object>).Method.GetGenericMethodDefinition().MakeGenericMethod(type)) as Func<object, object>;
}
/// <summary>
/// 克隆一个类
/// </summary>
/// <typeparam name="TValue"></typeparam>
/// <param name="value"></param>
/// <returns></returns>
private static object CloneAs<TValue>(object value)
{
return Copier<TValue>.Clone((TValue)value);
}
/// <summary>
/// 生成一份指定数据的克隆体
/// </summary>
/// <typeparam name="TValue">数据的类型</typeparam>
/// <param name="value">需要克隆的值</param>
/// <returns>克隆后的数据</returns>
public static TValue Clone<TValue>(TValue value)
{
if (value == null)
{
return value;
}
return Copier<TValue>.Clone(value);
}
/// <summary>
/// 辅助类,完成数据克隆
/// </summary>
/// <typeparam name="TValue">数据类型</typeparam>
private static class Copier<TValue>
{
/// <summary>
/// 用于克隆的句柄
/// </summary>
internal static readonly Func<TValue, TValue> Clone;
/// <summary>
/// 初始化
/// </summary>
static Copier()
{
MethodFactory<Func<TValue, TValue>> method = MethodFactory.Create<Func<TValue, TValue>>();
Type type = typeof(TValue);
if (type == typeof(object))
{
method.LoadArg(0).Return();
return;
}
switch (Type.GetTypeCode(type))
{
case TypeCode.Object:
if (type.IsClass)
{
method.LoadArg(0).Call(Reflector.GetMethod(typeof(object), "MemberwiseClone")).Cast(typeof(object), typeof(TValue)).Return();
}
else
{
method.LoadArg(0).Return();
}
break;
default:
method.LoadArg(0).Return();
break;
}
Clone = method.Delegation;
}
}
#endregion
这是一个小型的辅助类,使用反射来访问MemberwiseClone
,并缓存委托以避免过多地使用反射。
public static class CloneUtil<T>
{
private static readonly Func<T, object> clone;
static CloneUtil()
{
var cloneMethod = typeof(T).GetMethod("MemberwiseClone", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic);
clone = (Func<T, object>)cloneMethod.CreateDelegate(typeof(Func<T, object>));
}
public static T ShallowClone(T obj) => (T)clone(obj);
}
public static class CloneUtil
{
public static T ShallowClone<T>(this T obj) => CloneUtil<T>.ShallowClone(obj);
}
Person b = a.ShallowClone();
MemberwiseClone需要较少的维护。我不知道默认属性值是否有所帮助,也许可以忽略具有默认值的项目。