Search code examples
c#deep-copyconcurrentdictionary

Make a deep copy of an object inside a ConcurrentDictionary in C#


This is a follow up question to my previous one, Deep copy a ConcurrentDictionary in C#, which did not contain sufficient information for it to be properly answered.

I am trying to make a deep copy of an object, Patient, in a ConcurrentDictionary, _baselinePatients, to another ConcurrentDictionary, _patientsCurrentArm, in C#, but failing. It seems as if the patient that I copy from _baselinePatients to _patientsCurrentArm is just a pointer, despite using MemberwiseClone when cloning the patient.

Does it have to do with the fact that the two ConcurrentDictionaries _baselinePatients and _patientsCurrentArm contain the same data types?

See my comment on line 47 in the code below where I get this error: System.ArgumentException: 'An item with the same key has already been added. Key: Covariate'

using System.Collections.Concurrent;
namespace ExampleCode;
internal class Program
{
    private static void Main(string[] args)
    {
        Exampel obj = new();
        obj.DoStuff();
    }
}

internal class Exampel()
{
    /// <summary>
    /// For the baseline patients (patient ID, patient object), same for all arms and only generated once
    /// </summary>
    private ConcurrentDictionary<int,
                                 Patient> _baselinePatients = new();

    /// <summary>
    /// Patients in the current arm (patient ID, patient object)
    /// </summary> 
    private ConcurrentDictionary<int,
                                 Patient> _patientsCurrentArm = new();

    public void DoStuff()
    {
        const int EXAMPLE_CYCLE = 0;
        // Set the number of arms and patients
        int _nArms = 2;
        int _nPatients = 5;

        // Add patients at baseline to _baselinePatients
        for (int i = 0; i < _nPatients; i++) {
            Patient patient = new Patient();
            patient.PatientCovariates.Add(0, new());
            _baselinePatients.TryAdd(i, patient);
        }

        // Try to copy the patients created in the baseline ConcurrentDictionary
        for (int i = 0; i < _nArms; i++) {
            for (int j = 0; j < _nPatients; j++) {
                Patient patObjCopy = (Patient)_baselinePatients[j].Clone();
                Patient patObjCopyTwo = (Patient)patObjCopy.Clone();

                // patObjCopy should not be the same as patObjCopyTwo
                // for i = 1 and j = 0 I get this error here: System.ArgumentException: 'An item with the same key has already been added. Key: Covariate'

                patObjCopy.PatientCovariates[EXAMPLE_CYCLE].Add("Covariate for patient ",    // To mark the arm name and make the patient struct different
                                                                j.ToString() + 
                                                                " in arm " + 
                                                                i.ToString());

                // Save the patient copy in the _patientsCurrentArm WITHOUT changing the patient in _baselinePatients
                _patientsCurrentArm.TryAdd(j, patObjCopy);
            }
        }

        // Print out the value of Covariate for the first and second patient in EXAMPLE_CYCLE 
        Console.WriteLine(_patientsCurrentArm[0].PatientCovariates[EXAMPLE_CYCLE]["Covariate"]);
        Console.WriteLine(_patientsCurrentArm[1].PatientCovariates[EXAMPLE_CYCLE]["Covariate"]);
    }

    internal struct Patient : ICloneable
    {
        /// <summary>
        /// Cycle, covariate name, covariate value
        /// </summary>
        public Dictionary<int,
                          Dictionary<string,
                                     string>> PatientCovariates
        { get; set; }


        /// <summary>
        /// Initiates PatientCovariates
        /// </summary>
        public Patient()
        {
            PatientCovariates = new();
        }

        /// <summary>
        /// Clone patients
        /// </summary>
        /// <returns></returns>
        public readonly object Clone()
        {
            Patient patient = (Patient)this.MemberwiseClone();
            return patient;
        }
    }
}

Solution

  • The issue has to do with how C# distinguishes Value Types and Reference Types. A reference type is always stored on the heap (if you are coming from C++ you can think of it as always a pointer). When you copy one (via the equal operator or memberwise clone) you are copying the value of the pointer, not making a deep copy of the object.

    Value types are always the opposite -- copying one will clone the object itself. (There are more differences between value and reference types, but those are the most relevant to your question).

    Here is one way you could clone your Dictionary:

        public readonly object Clone()
        {
           Patient patient = new() {
               PatientCovariates = this.PatientCovariates.ToDictionary(
                   entry => entry.Key,
                   entry => entry.Value.ToDictionary(
                       innerEntry => innerEntry.Key,
                       innerEntry => innerEntry.Value
                   )
           )};
           return patient;
        }
    

    Your call to Patient.MemberwiseClone() produces a copy of patient where the reference (pointer) to PatientCovariates has been copied, but the actual dictionary has not been copied.

    If you want a unique and seperate copy of that dictionary you will need to write code in your Clone method to do so.