< Summary

Information
Class: MyNet.Reflection.ObjectGraphMapper
Assembly: MyNet.Reflection
File(s): https://raw.githubusercontent.com/sandre58/MyNet/85372080fe102cd9ee155ceab49ae000e7f66103/src/MyNet.Reflection/ObjectGraphMapper.cs
Tag: 323_28699572109
Line coverage
84%
Covered lines: 49
Uncovered lines: 9
Coverable lines: 58
Total lines: 193
Line coverage: 84.4%
Branch coverage
73%
Covered branches: 34
Total branches: 46
Branch coverage: 73.9%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.cctor()100%11100%
Clone(...)75%4483.33%
CloneInternal(...)75%201675%
CopyFields(...)83.33%66100%
CopyArrayElements(...)50%2280%
Populate(...)50%22100%
PopulateInternal(...)71.42%161476.92%
ShouldPreserveReferences(...)100%22100%

File(s)

https://raw.githubusercontent.com/sandre58/MyNet/85372080fe102cd9ee155ceab49ae000e7f66103/src/MyNet.Reflection/ObjectGraphMapper.cs

#LineLine coverage
 1// -----------------------------------------------------------------------
 2// <copyright file="ObjectGraphMapper.cs" company="Stéphane ANDRE">
 3// Copyright (c) Stéphane ANDRE. All rights reserved.
 4// </copyright>
 5// -----------------------------------------------------------------------
 6
 7using System;
 8using System.Collections.Generic;
 9using System.Reflection;
 10
 11namespace MyNet.Reflection;
 12
 13internal static class ObjectGraphMapper
 14{
 315    private static readonly MethodInfo CloneMethod = typeof(object).GetMethod("MemberwiseClone", BindingFlags.Instance |
 16
 17    /// <summary>
 18    /// Creates a deep clone of the specified object, recursively copying all fields and properties. If the <paramref na
 19    /// </summary>
 20    /// <param name="obj">The object to clone.</param>
 21    /// <param name="options">The options for deep cloning.</param>
 22    /// <typeparam name="T">The type of the object to clone.</typeparam>
 23    /// <returns>A deep clone of the specified object.</returns>
 24    public static T? Clone<T>(T? obj, DeepCloneOptions? options = null)
 25    {
 926        if (obj is null)
 027            return default;
 28
 929        var visited = ShouldPreserveReferences(options)
 930            ? new Dictionary<object, object?>(ReferenceEqualityComparer.Instance)
 931            : null;
 32
 933        return (T?)CloneInternal(obj, visited);
 34    }
 35
 36    /// <summary>
 37    /// Recursively clones the specified object, handling reference preservation if a visited dictionary is provided. Th
 38    /// </summary>
 39    /// <param name="obj">The object to clone.</param>
 40    /// <param name="visited">A dictionary to track visited objects for reference preservation.</param>
 41    /// <returns>A deep clone of the specified object.</returns>
 42    private static object? CloneInternal(object? obj, IDictionary<object, object?>? visited)
 43    {
 7244        if (obj is null)
 045            return null;
 46
 7247        var type = obj.GetType();
 48
 7249        if (type.IsSimple())
 3650            return obj;
 51
 3652        if (visited != null && visited.TryGetValue(obj, out var existing))
 1253            return existing;
 54
 2455        if (obj is Delegate)
 056            return obj;
 57
 2458        var clone = CloneMethod.Invoke(obj, null);
 59
 2460        visited?[obj] = clone;
 61
 2462        if (obj is Array sourceArray && clone is Array clonedArray)
 63        {
 064            CopyArrayElements(sourceArray, clonedArray, visited);
 065            return clone;
 66        }
 67
 2468        CopyFields(type, obj, clone, visited);
 69
 2470        return clone;
 71    }
 72
 73    /// <summary>
 74    /// Copies the fields of the specified type from the source object to the target object, recursively cloning field v
 75    /// </summary>
 76    /// <param name="type">The type whose fields are to be copied.</param>
 77    /// <param name="source">The source object from which to copy field values.</param>
 78    /// <param name="target">The target object to which field values are copied.</param>
 79    /// <param name="visited">A dictionary to track visited objects for reference preservation.</param>
 80    private static void CopyFields(Type? type, object source, object? target, IDictionary<object, object?>? visited)
 81    {
 8182        while (type != null)
 83        {
 5484            var fields = type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
 85
 18086            foreach (var field in fields)
 87            {
 3688                if (field.IsDefined(typeof(NonSerializedAttribute), true))
 89                    continue;
 90
 3691                var value = field.GetValue(source);
 3692                var cloned = CloneInternal(value, visited);
 93
 3694                field.SetValue(target, cloned);
 95            }
 96
 5497            type = type.BaseType;
 98        }
 2799    }
 100
 101    /// <summary>
 102    /// Recursively copies the elements of an array from the source to the target, cloning each element as needed while 
 103    /// </summary>
 104    /// <param name="source">The source array whose values are copied.</param>
 105    /// <param name="target">The target array that receives the cloned values.</param>
 106    /// <param name="visited">A dictionary to track visited objects for reference preservation.</param>
 107    private static void CopyArrayElements(Array source, Array target, IDictionary<object, object?>? visited)
 108    {
 9109        if (source.Rank != target.Rank)
 0110            throw new ArgumentException("Source and target arrays must have the same rank.", nameof(target));
 111
 9112        var indices = new int[source.Rank];
 113
 9114        copyDimension(0);
 9115        return;
 116
 117        void copyDimension(int dimension)
 118        {
 119            var sourceLowerBound = source.GetLowerBound(dimension);
 120            var sourceUpperBound = source.GetUpperBound(dimension);
 121
 122            if (sourceLowerBound != target.GetLowerBound(dimension) || sourceUpperBound != target.GetUpperBound(dimensio
 123                throw new ArgumentException("Source and target arrays must have the same bounds.", nameof(target));
 124
 125            for (var i = sourceLowerBound; i <= sourceUpperBound; i++)
 126            {
 127                indices[dimension] = i;
 128
 129                if (dimension < source.Rank - 1)
 130                {
 131                    copyDimension(dimension + 1);
 132                }
 133                else
 134                {
 135                    var value = source.GetValue(indices);
 136                    var cloned = CloneInternal(value, visited);
 137                    target.SetValue(cloned, indices);
 138                }
 139            }
 140        }
 141    }
 142
 143    /// <summary>
 144    /// Recursively populates the fields of the destination object with values from the source object, handling referenc
 145    /// </summary>
 146    /// <param name="source">The source object from which to copy field values.</param>
 147    /// <param name="destination">The destination object to which field values are copied.</param>
 148    /// <param name="options">Options to control the cloning behavior.</param>
 149    public static void Populate(object source, object destination, DeepCloneOptions? options = null)
 150    {
 12151        ArgumentNullException.ThrowIfNull(source);
 12152        ArgumentNullException.ThrowIfNull(destination);
 153
 12154        var visited = ShouldPreserveReferences(options)
 12155            ? new Dictionary<object, object?>(ReferenceEqualityComparer.Instance)
 12156            : null;
 157
 12158        PopulateInternal(source, destination, visited);
 12159    }
 160
 161    /// <summary>
 162    /// Recursively populates the fields of the destination object with values from the source object, handling referenc
 163    /// </summary>
 164    /// <param name="source">The source object from which to copy field values.</param>
 165    /// <param name="target">The target object to which field values are copied.</param>
 166    /// <param name="visited">A dictionary to track visited objects for reference preservation.</param>
 167    public static void PopulateInternal(object source, object target, IDictionary<object, object?>? visited)
 168    {
 12169        var type = source.GetType();
 170
 12171        if (type.IsSimple())
 0172            return;
 173
 12174        if (visited?.TryGetValue(source, out _) == true)
 0175            return;
 176
 12177        if (source is Delegate)
 0178            return;
 179
 12180        visited?[source] = target;
 181
 12182        if (source is Array sourceArray && target is Array targetArray)
 183        {
 9184            CopyArrayElements(sourceArray, targetArray, visited);
 9185            return;
 186        }
 187
 3188        CopyFields(type, source, target, visited);
 3189    }
 190
 21191    private static bool ShouldPreserveReferences(DeepCloneOptions? options) => options?.PreserveReferences ?? true;
 192}
 193