Самый быстрый способ сделать неглубокое копирование в C#

Интересно, какой самый быстрый способ сделать мелкое копирование на С#? Я только знаю, что есть 2 способа сделать мелкую копию:

  1. MemberwiseClone
  2. Скопируйте каждое поле по одному (вручную)

Я обнаружил, что (2) быстрее, чем (1). Мне интересно, есть ли другой способ сделать поверхностное копирование?


person tep    schedule 08.06.2009    source источник
comment
Это поверхностная копия, если копирование не выполняется рекурсивно.   -  person Daniel Brückner    schedule 08.06.2009
comment
Я хочу сделать поверхностную копию на самом деле   -  person tep    schedule 08.06.2009
comment
Неглубокая копия — это когда вы дублируете ссылки на данные (поэтому обе копии ссылаются на общую версию данных). Глубокая копия — это когда вы дублируете все фактические данные (так что есть две независимые копии всего).   -  person Jason Williams    schedule 08.06.2009
comment
Если вам нужно много неглубоких копий, возможно, использование структуры вместо класса будет более подходящим?   -  person Luaan    schedule 19.02.2014


Ответы (8)


Это сложная тема с множеством возможных решений и множеством плюсов и минусов для каждого. Существует замечательная статья здесь, в которой описываются несколько различных способов создания копии в С#. Обобщить:

  1. Клонировать вручную
    Утомительно, но высокий уровень контроля.

  2. Клонирование с помощью MemberwiseClone
    Создает только поверхностную копию, т. е. для полей ссылочного типа исходный объект и его клон ссылаются на один и тот же объект.

  3. Клонирование с отражением
    Неглубокое копирование по умолчанию, его можно переписать для глубокого копирования. Преимущество: автоматизировано. Недостаток: отражение медленное.

  4. Клонирование с сериализацией
    Простота, автоматизация. Откажитесь от некоторого контроля, и сериализация будет самой медленной из всех.

  5. Клонировать с помощью IL, клонировать с помощью методов расширения
    Более продвинутые решения, не такие распространенные.

person Nick Stamas    schedule 08.06.2009
comment
Ясно... Среди всех доступных вариантов я думаю, что (1) самый быстрый. Спасибо! - person tep; 08.06.2009
comment
@tep нет, MemberwiseClone должен быть самым быстрым. Ваши результаты, вероятно, были вызваны режимом отладки/релиза или каким-либо другим фактором в этом роде. - person Mr. TA; 22.04.2013
comment
MemberwiseClone выполняет некоторые дополнительные проверки и тому подобное, поэтому для объектов с небольшим количеством полей это может быть немного медленнее. В любом случае, вам следует заботиться об этом только в том случае, если вы действительно испытываете проблемы с производительностью. Не оптимизируйте по наитию, профиль. Если вы так много клонируете, что у вас возникают проблемы с MemberwiseClone, вам, вероятно, следует использовать структуры, а не классы. Или, возможно, нужны еще более агрессивные изменения в архитектуре :D - person Luaan; 19.02.2014
comment
Вы взглянули на библиотеку Fasterflect? У него есть хороший вариант для глубокой сериализации. - person Alkampfer; 31.08.2016

Хочу начать с нескольких цитат:

Фактически, 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, что возможно, потому что это преобразуемый тип.

Чтобы протестировать самостоятельно, скомпилируйте с небезопасным кодом, отключите подавление JIT, скомпилируйте режим выпуска и протестируйте. Я также указал время после каждой соответствующей строки.

Реализация 1:

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

По сути, я запускал эти тесты несколько раз, проверял вывод сборки, чтобы убедиться, что вещь не была оптимизирована, и т. д. Конечным результатом является то, что я приблизительно знаю, сколько секунд стоит эта одна строка кода, что составляет 0,40 с на мой компьютер. Это наш базовый уровень с использованием MemberwiseClone.

Реализация 2:

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

Если вы внимательно посмотрите на эти цифры, вы заметите несколько вещей:

  • Создание объекта и его копирование займет примерно 0,20 с. При нормальных обстоятельствах это самый быстрый код, который вы можете иметь.
  • Однако для этого вам нужно закрепить и открепить объект. Это займет у вас 0,81 секунды.

Так почему же все это так медленно?

Мое объяснение состоит в том, что это связано с GC. По сути, реализации не могут полагаться на то, что память останется неизменной до и после полной сборки мусора (адрес памяти может быть изменен во время сборки мусора, что может произойти в любой момент, в том числе во время вашей мелкой копии). Это означает, что у вас есть только 2 возможных варианта:

  1. Закрепление данных и копирование. Обратите внимание, что GCHandle.Alloc — это только один из способов сделать это, хорошо известно, что такие вещи, как C++/CLI, дадут вам лучшую производительность.
  2. Перечисление полей. Это гарантирует, что между сборками мусора вам не нужно будет делать ничего особенного, а во время сборов мусора вы сможете использовать возможности сборщика мусора для изменения адресов в стеке перемещаемых объектов.

MemberwiseClone будет использовать метод 1, что означает снижение производительности из-за процедуры закрепления.

(Намного) более быстрая реализация

Во всех случаях наш неуправляемый код не может делать предположения о размере типов и должен закреплять данные. Предположения о размере позволяют компилятору лучше выполнять оптимизацию, например развертывание цикла, выделение регистров и т. д. (точно так же, как ctor копирования C++ работает быстрее, чем memcpy). Отсутствие необходимости закреплять данные означает, что мы не получаем дополнительного удара по производительности. Поскольку .NET JIT относится к ассемблеру, теоретически это означает, что мы должны иметь возможность сделать более быструю реализацию, используя простую передачу IL и позволив компилятору оптимизировать ее.

Итак, подведем итог, почему это может быть быстрее, чем нативная реализация?

  1. Он не требует закрепления объекта; объекты, которые перемещаются, обрабатываются сборщиком мусора — и на самом деле он безжалостно оптимизируется.
  2. Он может делать предположения о размере копируемой структуры и, следовательно, позволяет лучше распределять регистры, развертывать циклы и т. д.

Мы стремимся к производительности 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);
    }
}

Я протестировал этот код с результатом: 0,16 с. Это означает, что он примерно в 2,5 раза быстрее, чем MemberwiseClone.

Что еще более важно, эта скорость соответствует memcpy, что является более или менее «оптимальным решением при нормальных обстоятельствах».

Лично я считаю, что это самое быстрое решение, и самое приятное то, что если среда выполнения .NET ускорится (надлежащая поддержка инструкций SSE и т. д.), то и это решение ускорится.

Примечание редакции. В приведенном выше примере кода предполагается, что конструктор по умолчанию является общедоступным. Если это не так, вызов GetConstructor возвращает null. В этом случае используйте одну из других подписей GetConstructor для получения защищенных или закрытых конструкторов. См. https://docs.microsoft.com/en-us/dotnet/api/system.type.getconstructor?view=netframework-4.8

person atlaste    schedule 20.07.2015
comment
Хм. Вы проверяли этот вызов Cloner<BaseClass>.Clone(someDerivedObject);? Я предполагаю, что это не удастся, потому что вы используете статическую информацию поля времени компиляции для создания клонера. Реализации клонов часто определяются в базовом классе (а-ля MemberwiseClone), который не может знать о будущих производных классах, но все равно должен копировать все поля вниз по иерархии. - person Bruce Pierson; 21.08.2015
comment
@BrucePierson Ну да, это не так, верно? Тем не менее, если это ваш сценарий, конечно, довольно легко использовать this.GetType(). Лично я стараюсь не загромождать базовые классы подобными служебными функциями. - person atlaste; 21.08.2015
comment
Но поскольку это статический универсальный класс, this.GetType() бесполезен — вы не можете использовать вывод типа здесь, не так ли? - person Bruce Pierson; 21.08.2015
comment
@BrucePierson Вы меня неправильно поняли. Вы можете изменить все вхождения typeof(T) на someObject.GetType() в коде и сохранить данные в словаре (вам нужна блокировка). Это просто съест вашу производительность. В качестве альтернативы вы можете создать клон abstract, переопределить клон и вызвать мой код, что намного быстрее, или вызвать его напрямую, а не помещать в базовый класс (что я бы и сделал) - в конце концов, эта тема была о производительности. - person atlaste; 21.08.2015
comment
Да я вижу. Действительно, если сделать тип необобщенным и применить параметр универсального типа к методу Clone, это сработает, но я очень сомневаюсь, что на этом этапе будет какой-либо прирост производительности даже при использовании словаря O:1. В любом случае, +1 за очень интересное описание, хотя, возможно, вам следует добавить некоторые из этих предостережений в качестве примечаний, чтобы кто-то не воспринял это как полную замену MemberwiseClone... - person Bruce Pierson; 21.08.2015
comment
@BrucePierson Я действительно протестировал описанный вами подход к словарю - есть огромная разница в производительности. Кроме того, для меня не имеет особого смысла помещать клон в базовый класс... Цитируя Майка Актона (ссылка на его хороший доклад на cppcon): программное обеспечение должно иметь смысл для машин, а не для людей. IMO Обычно вы знаете, что клонировать, потому что функция-член запрашивает клонирование - с другой стороны, нет особого смысла забывать эту часть информации, а затем снова выяснять это, выполняя поиск по словарю... Просто мои 2 цента. .. - person atlaste; 21.08.2015
comment
Это отличный ответ. Но одна вещь мне все еще не ясна: если этот метод намного быстрее, чем MemberwiseClone, почему бы CLR не сделать это вместо этого? - person Chris Gillum; 23.05.2016
comment
@ChrisGillum Я нахожу забавным предположение, что .NET нацелен на производительность. Если вы спросите меня, .NET в первую очередь реализуется с учетом «ремонтопригодности» и «правильности». Просто в качестве примера: лично мне наплевать на наличие ArgumentNullException или NullReferenceException (да, я знаю разницу) - только это уже составляет тысячи проверок в среде выполнения .NET с эффектами предсказания переходов и т. д. Лично , мне довольно легко превзойти .NET по производительности. Однако с точки зрения разработки программного обеспечения мне очень трудно превзойти его. - person atlaste; 25.05.2016
comment
@atlaste Итак, если я понимаю, о чем вы говорите, разница в производительности между этими двумя вариантами в основном связана со стоимостью проверки правильности и т. Д. Лично у меня нет проблем с балансом производительности и продуктивности разработчиков - команда .NET безусловно, очень заботятся о производительности, но должны сбалансировать ее с общей производительностью разработчиков (и я думаю, что в целом они делают правильный выбор). Спасибо. - person Chris Gillum; 25.05.2016
comment
@ChrisGillum Не совсем. Они просто сосредоточены на продуктивности разработчиков и обеспечивают разумную производительность. Если вы, как и я, привыкли программировать на низкоуровневом C++, то превзойти приложение .NET в 10 раз не так уж и сложно... - person atlaste; 25.05.2016
comment
@atlaste - Спасибо за то, что кажется очень тщательным анализом и защитой предложенного вами решения. Я не знаю, является ли он самым быстрым, но я чувствую, что это решение для мелкой копии обеспечивает отличный баланс между универсальностью и отделением от базового класса и производительностью. Когда это возможно, я тоже считаю, что предпочтительнее избегать беспорядка в классах для решения общих проблем. Поэтому, если мне нужна универсальная операция поверхностного копирования, я думаю, что с этого момента я буду использовать ее. - person porcus; 16.02.2017
comment
Вызов GetConstructor, использованный в примере, находит только общедоступные конструкторы. Используйте другую сигнатуру, чтобы найти конструкторы, объявленные с другим модификатором доступа. - person Paul Chernoch; 04.10.2019
comment
@PaulChernoch Это было сделано намеренно. Если люди прячут конструкторы, они обычно делают это намеренно; возьмем, к примеру, System.IO.File. Тем не менее, еще лучше было бы добавить специализированный конструктор с хорошо известным типом (скажем, MyConstructor(ShallowCloneable arg)) — таким образом, разработчик всегда знает, что произойдет, даже если аргумент не используется ни для чего, кроме устранения неоднозначности. (чтобы убедиться, что конструктор не инициализирует ничего, что мы не хотим, чтобы он инициализировал). Вы поняли суть... - person atlaste; 04.10.2019
comment
@atlaste - К сожалению, иногда мы сталкиваемся со сторонними библиотеками, которые не предоставляют ни операции клонирования, ни общедоступного конструктора по умолчанию, и нам необходимо клонировать объекты. Ваш измененный пример кода допускает этот вариант использования. - person Paul Chernoch; 04.10.2019
comment
@PaulChernoch Да, в некоторых сценариях вы можете это сделать ... Просто будьте осторожны с этим; вы можете в конечном итоге клонировать синглтон или неуправляемый ресурс... - person atlaste; 07.10.2019

Я запутался. MemberwiseClone() должен аннулировать производительность всего остального для мелкой копии. В CLI любой тип, кроме RCW, должен иметь возможность поверхностного копирования в следующей последовательности:

  • Выделить память в питомнике под тип.
  • memcpy данные из оригинала в новые. Поскольку цель находится в питомнике, барьеры записи не требуются.
  • If the object has a user-defined finalizer, add it to the GC list of items pending finalization.
    • If the source object has SuppressFinalize called on it and such a flag is stored in the object header, unset it in the clone.

Может ли кто-нибудь из группы внутренней разработки CLR объяснить, почему это не так?

person Sam Harwell    schedule 20.08.2009
comment
@ta.speot.is, вздор. Это хорошая тема для обсуждения, и большинство из нас участвуют в этих дискуссиях, чтобы лучше понять ситуацию в целом. - person Nicholas Petersen; 28.08.2014
comment
@SamHaswell MemberwiseClone — это вызов external, что означает, что он требует закрепления. Закрепление — относительно медленный процесс. Я опубликовал подробности своих тестов и результатов в своем ответе ниже. У меня больше нет данных эталонного теста нативных вызовов, но я почти уверен, что закрепление/собственное взаимодействие и дополнительные вызовы vtable (clone, sizeof) учитывают накладные расходы irt. memcpy, который вы нашли. - person atlaste; 20.07.2015

Зачем все усложнять? 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();
}
person jun estevez    schedule 08.03.2013

Это способ сделать это с помощью динамической генерации IL. Я нашел это где-то в Интернете:

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);
    }
}
person eulerfx    schedule 08.06.2009
comment
Поскольку здесь используется отражение, вряд ли это будет быстрее, чем object.MemberwiseClone (выполняется внутри среды CLR). Я рекомендую OP просто использовать MemberwiseClone в большинстве ситуаций или копировать поля вручную для каждого класса, если требуется высокая производительность. - person Noldorin; 08.06.2009
comment
К вашему сведению, это не будет работать с цепочками наследования, так как вам также нужно захватить базовые типы. - person Sam Saffron; 11.01.2012
comment
@Noldorin неверно, он использует отражение только для создания DynamicMethod - последующие вызовы выполняются так же быстро, как ручное клонирование. Однако это все еще медленнее, чем MemberwiseClone, если, конечно, .NET реализует его правильно, используя операцию типа memcpy. - person Mr. TA; 22.04.2013
comment
@Mr.TA: Не говорите неправильно, если вы не противоречите тому, что я сказал на самом деле. Хорошо, использование отражения минимально, а замедление, кроме первого вызова, связано в основном с используемыми управляемыми вызовами, но все же. - person Noldorin; 22.04.2013
comment
Просто примечание: вы можете избежать статического словаря, указав, что весь класс является универсальным. У вас будет только одно статическое поле в универсальном классе для делегата. Фактически, у вас также может быть синглтон, реализующий интерфейс ICloneable, который вы можете динамически создавать, что будет даже быстрее, чем делегат :)) - person Luaan; 19.02.2014

Фактически, MemberwiseClone обычно намного лучше других, особенно для сложного типа.

Причина в том, что: если вы вручную создаете копию, она должна вызывать один из конструкторов типа, но использовать клонирование по элементам, я думаю, это просто копирует блок памяти. для этих типов есть очень дорогие действия построения, клонирование по члену - абсолютно лучший способ.

Однажды я написал такой тип: {string A = Guid.NewGuid().ToString()}, я обнаружил, что клонирование по элементам намного быстрее, чем создание нового экземпляра и ручное назначение членов.

Результат кода ниже:

Копирование вручную: 00:00:00.0017099

MemberwiseClone:00:00:00.0009911

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
person dexiang    schedule 19.02.2014

MemberwiseClone требует меньше обслуживания. Я не знаю, помогают ли значения свойств по умолчанию, может быть, если бы можно было игнорировать элементы со значениями по умолчанию.

person Eric Schneider    schedule 08.06.2009

Вот небольшой вспомогательный класс, который использует отражение для доступа к 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();
person Tim Pohlmann    schedule 24.10.2018