Location>code7788 >text

C#|.net core Fundamentals - Five Classes and N Implementations of Deep Copies

Popularity:535 ℃/2024-09-21 20:42:15

In practice, there are often such needs: to obtain an accurate copy of the original object with the same data, but independent of the original object, in short, a clone, a copy, a copy of the same object and the original object, but the two various modifications can not affect each other. This behavior is also called deep cloning, deep copy.

Copying objects in C# is a seemingly simple but actually quite complex things, so I do not recommend to do their own encapsulation methods and then the project to use, there are too many pitfalls, easy to get out of the problem. Below to share with you the five categories N deep copy method.

The first category, for the simple reference type way

This type of method is only valid for simple reference types, not if the type contains an attribute field of a reference type.

1. MemberwiseClone method

MemberwiseClone is a way to create a copy of the current object'sshallow copyThis method is not suitable for deep copying. It is not inherently suitable for deep copying, but this method can be used for deep copying of simple reference types that do not contain reference type attribute fields. Since this method is a protected method of type Obejct, it can only be used inside a class.

The sample code is as follows:

public class MemberwiseCloneModel
{
    public int Age { get; set; }
    public string Name { get; set; }
    public MemberwiseCloneModel Clone()
    {
        return (MemberwiseCloneModel)();
    }
}
public static void NativeMemberwiseClone()
{
    var original = new MemberwiseCloneModel();
    var clone = ();
    (original == clone);
    (ReferenceEquals(original, clone));
}

2、with expression

Most of you may be confused when you see the with expression, what does it have to do with deep copy? It has to do with record, which was introduced in C# 9, when it could only be declared as a value type record by record struct, and in C# 10, record class was introduced to declare a reference type record. There may still be a lot of people do not understand record, simply put, it is used to define immutable data objects, is a special type.

with can be applied to the right side of a record instance to create a new record instance, which has the same problem as MemberwiseClone in that if the object contains a reference type property member, only its properties are copied. Therefore, you can only make deep copies of simple reference types. The example code is as follows:

public record class RecordWithModel
{
    public int Age { get; set; }
    public string Name { get; set; }
}
public static void NativeRecordWith()
{
    var original = new RecordWithModel();
    var clone = original with { };
    (original == clone);
    (ReferenceEquals(original, clone));
}

Type II, manual mode

These are simple yet complex methods that require manual processing.

1、Pure handmade

Purely manual is an attribute field one by one, to be honest, I like this way, the whole process is completely controllable, troubleshooting is very convenient at a glance, of course, if you encounter complex multiple nested types is also a headache. Look at the code to feel.

public class CloneModel
{
    public int Age { get; set; }
    public string Name { get; set; }
    public List<CloneModel> Models { get; set; }
}
public static void ManualPure()
{
    var original = new CloneModel
    {
        Models = new List<CloneModel>
        {
            new() 
            {
                Age= 1,
                Name="1"
            }
        }
    };
    var clone = new CloneModel
    {
        Age = ,
        Name = ,
        Models = (x => new CloneModel
        {
            Age = ,
            Name = ,
        }).ToList()
    };
    (original == clone);
    (ReferenceEquals(original, clone));
}

2、ICloneable interface

First of all, this is a built-in interface, but also just the definition of the interface, the specific implementation or need to rely on their own implementation, so the theory and purely manual the same, can be the only benefit is that there is a unified definition of the specific implementation of this article can be used to achieve this interface, here is not at length.

The third category, the serialization method

This type of method is the core idea of serialization and then deserialization, which can also be divided into three subclasses: binary class, Xml class, Json class.

1. Binary serializer

1. (Enabled)

NET5 from the beginning of this method has been marked as deprecated, you can ignore this program, here to give you a reminder, for the old project you can refer to the following code.

public static T SerializeByBinary<T>(T original)
 {
     using (var memoryStream = new MemoryStream())
     {
         var formatter = new BinaryFormatter();
         (memoryStream, original);
         (0, );
         return (T)(memoryStream);
     }
 }

1.

The MessagePack package needs to be installed. The implementation is as follows:

public static T SerializeByMessagePack<T>(T original)
{
    var bytes = (original);
    return <T>(bytes);
}

2, Xml serializer

2.1. DataContractSerializer

Objects and members need to be defined using the [DataContract] and [DataMember] attributes, the sample code is as follows:

[DataContract]
public class DataContractModel
{
    [DataMember]
    public int Age { get; set; }
    [DataMember]
    public string Name { get; set; }
    [DataMember]
    public List<DataContractModel> Models { get; set; }
}
public static T SerializeByDataContract<T>(T original)
{
    using var stream = new MemoryStream();
    var serializer = new DataContractSerializer(typeof(T));
    (stream, original);
     = 0;
    return (T)(stream);
}

2.2. XmlSerializer

public static T SerializeByXml<T>(T original)
{
    using (var ms = new MemoryStream())
    {
        XmlSerializer s = new XmlSerializer(typeof(T));
        (ms, original);
         = 0;
        return (T)(ms);
    }
}

3, Json serializer

There are two famous Json serializers: Microsoft's own and (library installation required).

public static T SerializeByTextJson<T>(T original)
{
    var json = (original);
    return <T>(json);
}
public static T SerializeByJsonNet<T>(T original)
{
    var json = (original);
    return <T>(json);
}

Category IV, third-party repository approach

This type of method is simple to use, the program is mature, and is more suitable for use on projects.

1、AutoMapper

Installing the AutoMapper library

public static T ThirdPartyByAutomapper<T>(T original)
{
    var config = new MapperConfiguration(cfg =>
    {
        <T, T>();
    });
    var mapper = ();
    T clone = <T, T>(original);
    return clone;
}

2、DeepCloner

Install the DeepCloner library

public static T ThirdPartyByDeepCloner<T>(T original)
{
    return ();
}

3、FastDeepCloner

Installing the FastDeepCloner library

public static T ThirdPartyByFastDeepCloner<T>(T original)
{
    return (T)(original);
}

Category V. Expanding horizons approach

These types of methods are allSemi-finished methods, FYI, to provide ideas, expand horizons, not suitable for project use, of course, you can perfect them, a variety of special circumstances issues are dealt with is also available on the project.

1. Reflection

For example, the following does not deal with types such as dictionaries, tuples, and some other special cases.

public static T Reflection<T>(T original)
{
    var type = ();
    //If it is a value type、String or enumeration,Direct return
    if ( || || original is string)
    {
        return original;
    }
    //Handling collection types
    if (typeof(IEnumerable).IsAssignableFrom(type))
    {
        var listType = typeof(List<>).MakeGenericType(()[0]);
        var listClone = (IList)(listType);
        foreach (var item in (IEnumerable)original)
        {
            (Reflection(item));
        }
        return (T)listClone;
    }
    //Creating a new object
    var clone = (type);
    //Processing Fields
    foreach (var field in ( | | ))
    {
        var fieldValue = (original);
        if (fieldValue != null)
        {
            (clone, Reflection(fieldValue));
        }
    }
    //Processing attributes
    foreach (var property in ( | | ))
    {
        if ( && )
        {
            var propertyValue = (original);
            if (propertyValue != null)
            {
                (clone, Reflection(propertyValue));
            }
        }
    }
    return (T)clone;
}

2、Emit

The essence of Emit is to use C# to write IL code, these codes are more obscure and difficult to understand, later to find opportunities to explain separately. In addition here to add a caching mechanism to improve efficiency.

public class DeepCopyILEmit<T>
{
    private static Dictionary<Type, Func<T, T>> _cacheILEmit = new();
    public static T ILEmit(T original)
    {
        var type = typeof(T);
        if (!_cacheILEmit.TryGetValue(type, out var func))
        {
            var dymMethod = new DynamicMethod($"{}DoClone", type, new Type[] { type }, true);
            var cInfo = (new Type[] { });
            var generator = ();
            var lbf = (type);
            (, cInfo);
            (OpCodes.Stloc_0);
            foreach (FieldInfo field in ( |  | ))
            {
                (OpCodes.Ldloc_0);
                (OpCodes.Ldarg_0);
                (, field);
                (, field);
            }
            (OpCodes.Ldloc_0);
            ();
            func = (Func<T, T>)(typeof(Func<T, T>));
            _cacheILEmit.Add(type, func);
        }
        return func(original);
    }
}

3、Expression tree

Expression tree is a data structure, in the runtime will be compiled into IL code, the same code is more obscure, later to find opportunities to explain separately. In addition, a caching mechanism has been added here to improve efficiency.

public class DeepCopyExpressionTree<T>
{
    private static readonly Dictionary<Type, Func<T, T>> _cacheExpressionTree = new();
    public static T ExpressionTree(T original)
    {
        var type = typeof(T);
        if (!_cacheExpressionTree.TryGetValue(type, out var func))
        {
            var originalParam = (type, "original");
            var clone = (type, "clone");
            var expressions = new List<Expression>();
            ((clone, (type)));
            foreach (var prop in ())
            {
                var originalProp = (originalParam, prop);
                var cloneProp = (clone, prop);
                ((cloneProp, originalProp));
            }
            (clone);
            var lambda = <Func<T, T>>((new[] { clone }, expressions), originalParam);
            func = ();
            _cacheExpressionTree.Add(type, func);
        }
        return func(original);
    }
}

benchmarking

Finally, we perform a benchmark test comparing all the methods of the last three categories, each method executes three sets of tests, and the three sets test 100, 1000, and 10000 objects, respectively. The test model is:

[DataContract]
[Serializable]
public class DataContractModel
{
    [DataMember]
    public int Age { get; set; }
    [DataMember]
    public string Name { get; set; }
    [DataMember]
    public List<DataContractModel> Models { get; set; }
}

Where Models contains two elements. The final test results are as follows:

Through the results you can find: [ExpressionTree] and [Emit] > [AutoMapper] and [DeepCloner] > [MessagePack] > other

Tier 1: The best performers are [Expression Tree] and [Emit], which are very close to each other, fundamentally because they end up being IL code, which reduces the performance loss due to various reflections. So if you have extreme performance needs, you can improve based on these two programs to meet your needs.

Tier 2: The third-party libraries [AutoMapper] and [DeepCloner] are close behind in performance, are relatively good, and are mature libraries, so they can be prioritized if they are used on a project.

Third Tier: [MessagePack] is twice as bad as the second tier, but of course this one also requires the installation of third-party libraries.

Echelon 4: [] If you don't want to install additional libraries, there are no high performance requirements can consider using Microsoft's own Json serialization tools.

The other methods can be ignored.

classifier for sums of money: The test method code as well as the sample source code have been uploaded to the code repository for those who are interested./hugogoos/Planner