SoFunction
Updated on 2025-03-06

Use C# expression tree to implement deep cloning of objects (example details)

1. Basic concepts of expression trees

An expression tree is an expression represented in a tree-like structure, where each node represents a part of the expression. For example, an arithmetic expressiona + bcan be represented as a tree where the root node is an addition operator, and its two child nodes areaandb. In LINQ (Language Integrated Query), the expression tree enables the conversion of queries in C# into other forms of queries, such as SQL queries. In this way, the same query logic can be used for different types of data sources, such as databases, XML files, etc. Since expression trees can be created and modified at runtime, they are also very suitable for scenarios where code logic needs to be dynamically generated or changed based on runtime data. This is very useful for logic that requires repeated execution (such as deep clones mentioned in this article) because they can be optimized and cached, thereby increasing efficiency.

2. Create and use expression trees

In C#, we can passClasses in namespaces to create and manipulate expression trees. Here is an example of creating a simple expression tree:

// Create an expression tree to represent a + b        ParameterExpression a = (typeof(int), "a");
        ParameterExpression b = (typeof(int), "b");
        BinaryExpression body = (a, b);
        // Compile expression tree as executable code        var add = <Func<int, int, int>>(body, a, b).Compile();
        // Use expressions        (add(1, 2));  // Output 3

After we define a type, we can implement deep cloning through an anonymous delegate to perform value copying:

//Custom typepublic class TestDto
{
    public int Id { get; set; }
    public string Name { get; set; }
}
//Anonymous commissionFunc<TestDto, TestDto> deepCopy = x => new TestDto()
{
    Id = ,
    Name = 
};
//Use itvar a =new TestDto(){//Assign value};var b = deepCopy(a);//Implement deep cloning

Then if you want to automatically create this anonymous delegation, you will use the expression tree to automatically create anonymous delegation, so that you can realize complex automated expression creation without relying on performance-consuming methods such as reflection, serialization/deserialization, etc. The core business logic part is as follows: First of all, we need to know that the expression tree traverses the object's properties through reflection to implement the assignment operation such as x =. For different attributes such as arrays, dictionaries, value types, custom classes, and strings, the assignment schemes are different. We can directly assign simple value types and strings, because the assignments of both are "deep" cloned. That is, the variable modification after assignment will not affect the original variable. If complex dictionaries, arrays, and objects are assigned with =, they will only get references to the object, so different processing is required for different situations.

First, we need to define an interface ICloneHandler, and use a processing class that inherits the interface to handle it for different situations:

interface ICloneHandler
{
    bool CanHandle(Type type);//Is the current type possible?    Expression CreateCloneExpression(Expression original);//Generate expression tree for the current type}

Next we define an extension class and extension function for handling deep copy:

public static class DeepCloneExtension
{
    //Create a thread-safe cache dictionary and multiplex expression tree    private static readonly ConcurrentDictionary<Type, Delegate> cloneDelegateCache = new ConcurrentDictionary<Type, Delegate>();
    //Define all types that can be processed, and achieve scalability through policy mode    private static readonly List<ICloneHandler> handlers = new List<ICloneHandler>
    {
       //Add a custom type processor here    };
    /// <summary>
    /// Deep cloning function    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="original"></param>
    /// <returns></returns>
    public static T DeepClone<T>(this T original)
    {
        if (original == null)
            return default;
        // Get or create cloned expression        var cloneFunc = (Func<T, T>)(typeof(T), t => CreateCloneExpression<T>().Compile());
        //Call the expression and return the result        return cloneFunc(original);
    }
    /// <summary>
    /// Building the body logic of the expression tree    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <returns></returns>
    private static Expression<Func<T, T>> CreateCloneExpression<T>()
    {
        //Reflection Obtain Type        var type = typeof(T);
        // Create a parameter expression of type T 'x'        var parameterExpression = (type, "x");
        // Create a member binding list to store attribute bindings later        var bindings = new List<MemberBinding>();
        // traverse all properties of type T and select readable and writeable properties        foreach (var property in ().Where(prop =>  && ))
        {
            // Get the expression of the original attribute value            var originalValue = (parameterExpression, property);
            // Initialize an expression to store the attribute values ​​that may have been processed            Expression valueExpression = null;
            // Tag whether this attribute has been processed            bool handled = false;
            // traverse all processors and find processors that can handle the current attribute type            foreach (var handler in handlers)
            {
                // If you find a suitable processor, use it to create a cloned expression                if (())
                {
                    valueExpression = (originalValue);
                    handled = true;
                    break;
                }
            }
            // If no processor is found, use the original property value            if (!handled)
            {
                valueExpression = originalValue;
            }
            // Create a property binding            var binding = (property, valueExpression);
            // Add binding to the binding list            (binding);
        }
        // Use all attribute bindings to initialize a new T-type object        var memberInitExpression = ((type), bindings);
        // Create and return an expression tree that represents the conversion from the input parameter 'x' to the new object        return <Func<T, T>>(memberInitExpression, parameterExpression);
    }
}

Next we can add some common types of processors:

Array processing:

class ArrayCloneHandler : ICloneHandler
{
    Type elementType;
    public bool CanHandle(Type type)
    {
        //Array type needs to be processed specifically to obtain its internal type         = ();
        return ;
    }
    public Expression CreateCloneExpression(Expression original)
    {
        //Value type or string, assign value through value type array        if ( || elementType == typeof(string))
        {
            return (GetType().GetMethod(nameof(DuplicateArray),  | ).MakeGenericMethod(elementType), original);
        }
        // Otherwise, use the reference type to assign values        else
        {
            var arrayCloneMethod = GetType().GetMethod(nameof(CloneArray),  | ).MakeGenericMethod(elementType);
            return (arrayCloneMethod, original);
        }
    }
    //Array of reference type    static T[] CloneArray<T>(T[] originalArray) where T : class, new()
    {
        if (originalArray == null)
            return null;
        var length = ;
        var clonedArray = new T[length];
        for (int i = 0; i < length; i++)
        {
            clonedArray[i] = DeepClone(originalArray[i]);//Calling deep cloning expression of this type        }
        return clonedArray;
    }
    //Array of value type    static T[] DuplicateArray<T>(T[] originalArray)
    {
        if (originalArray == null)
            return null;
        T[] clonedArray = new T[];
        (originalArray, clonedArray, );
        return clonedArray;
    }
}

Custom type processing (actually, it is to call the deep clone of this type):

class ClassCloneHandler : ICloneHandler
{
    Type elementType;
    public bool CanHandle(Type type)
    {
         = type;
        return  && type != typeof(string);
    }
    public Expression CreateCloneExpression(Expression original)
    {
        var deepCloneMethod = typeof(DeepCloneExtension).GetMethod(nameof(DeepClone),  | ).MakeGenericMethod(elementType);
        return (deepCloneMethod, original);
    }
}

Then we can add these handles in the previous DeepCloneExtension

private static readonly List<ICloneHandler> handlers = new List<ICloneHandler>
{
    new ArrayCloneHandler(),//Array    new DictionaryCloneHandler(),//dictionary    new ClassCloneHandler()//kind    ...
};

Finally, we can implement deep cloning by simply making calls

var a = new TestDto() { Id = 1, Name = "Xiao Ming", Child = new TestDto() { Id = 2, Name = "Little Red" }, Record = new Dictionary<string, int>() { { "Grade 1", 1 }, { "Grade 2", 2 } }, Scores = [100, 95] };
var b = ();

In short, the expression tree of C# provides a powerful mechanism to represent the code in the form of a data structure, so that the code can be checked, modified or executed at runtime. This provides many possibilities for dynamic query generation, code optimization, and dynamic programming.

This is the end of this article about using C#'s powerful expression tree to implement deep cloning objects. For more related deep cloning content of C# objects, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!