Click here to Skip to main content
15,888,351 members
Please Sign up or sign in to vote.
0.00/5 (No votes)
Hey, I'm attempting to clone an object. The Update Student Scores form should create a clone of the current student object and then apply changes to the clone. That way the changes will be saved to the current student only if the user clicks the OK button. To create a clone, the Student class will need to implement the ICloneable interface, and the Clone method will need to implement a deep copy. So far, I've learned that you want a deep copy of something when you need a copy of the other objects contained by the original (rather than pointers to the location of those objects in the original), so that when you make changes to these copied properties, you don't affect the original object. What I'm struggling with though is applying it to my model. Below is the student class I'm trying to clone and the update student form. I've also attached the source code if that helps. Thanks!

SourceCode
GitHub - Triptonix/Help[^]

Student.cs
public class Student
    {
        public List<int> Scores = new List<int>();

        public string Name
        { get; set; }

        public bool AddScore(int score)
        {
            try
            {
                Scores.Add(score);
            }
            catch { return false; }
            return true;
        }

        public List<int> GetScores()
        {
            return Scores;
        }

        public int GetScoreAt(int index)
        {
            return (int)Scores[index];
        }

        public int GetScoreTotal()
        {
            int sum = 0;
            foreach (int score in Scores)
            {
                sum += score;
            }
            return sum;
        }

        public int GetScoreCount()
        {
            return Scores.Count;
        }

        public int GetScoreAverage()
        {
            return GetScoreTotal() / GetScoreCount();
        }

        public void DestroyScores()
        {
            Scores = new List<int>();
        }
    }

frmUpdateScores.cs
public partial class frmUpdateStudent : Form
   {
       private Form1 parentForm;  //main form
       private Student studentToEdit; //student list
       private int index; //index

       public frmUpdateStudent(Form1 parentForm, int index)  //update parent form (Form1) with the new student and scores
       {
           this.parentForm = parentForm;
           this.index = index;
           studentToEdit = this.parentForm.GetStudent(index);

           InitializeComponent();

           StudentName.Text = studentToEdit.Name;
           UpdateScoreDisplay();
       }

       public void AddScoreToStudent(int value) //add score to current student and display in the list
       {
           studentToEdit.AddScore(value);
           UpdateScoreDisplay();
       }

       public void UpdateScoreAtIndex(int id, int value)  //update a score selected from the list
       {
           studentToEdit.GetScores()[id] = value;
           UpdateScoreDisplay();
       }

       public int GetScoreAtIndex(int id)  //get the score index
       {
           return studentToEdit.GetScoreAt(id);
       }

       private void UpdateScoreDisplay()  //update the score display list
       {
           CurrentScores.DataSource = null;
           CurrentScores.DataSource = studentToEdit.GetScores();
       }

       private void AddScoreButton_Click(object sender, EventArgs e)  //open the add score form
       {
           frmAddScore addScoreForm = new frmAddScore(this);
           addScoreForm.Show();
       }

       private void RemoveScoreButton_Click_1(object sender, EventArgs e) //remove a score from current index and update display list
       {
           studentToEdit.GetScores().RemoveAt(CurrentScores.SelectedIndex);
           UpdateScoreDisplay();
       }

       private void ClearScoresButton_Click_1(object sender, EventArgs e) //clear all scores
       {
           studentToEdit.DestroyScores();
           UpdateScoreDisplay();
       }

       private void CloseButton_Click_1(object sender, EventArgs e)
       {
           Close();  //close form
       }

       private void UpdateButton_Click_1(object sender, EventArgs e)  //open update form for current student
       {
           Student Form1 = new Student();
           Form1.Name = StudentName.Text;
           parentForm.UpdateStudent(index, Form1);
           Close();
       }

       private void UpdateScoresButton_Click(object sender, EventArgs e)
       {
           frmUpdateScore updateScoreForm = new frmUpdateScore(this, CurrentScores.SelectedIndex);
           updateScoreForm.Show();
       }
   }


What I have tried:

I've tried to clone the class, but not sure how to apply the changes only to the clone so that way, the changes will be saved to the current student only if the user clicks the OK button
Posted
Updated 2-May-19 14:36pm
v2
Comments
[no name] 2-May-19 21:50pm    
Those in the know use MemberwiseClone() to clone the shallow parts ... since you don't have to revisit your "custom code" if you're only adding new (non-thick) properties.

https://docs.microsoft.com/en-us/dotnet/api/system.object.memberwiseclone?view=netframework-4.8

Value types are not referenced anyhow (int, bool). strings are immutable, they are also not referenced. If you have a class you want to deep copy, and it contains class instances, you need to build new instances of that class, or they will be referenced
 
Share this answer
 
Comments
Member 14351839 2-May-19 18:08pm    
But Clone() is a string method used to clone the string object? So your saying in my case since my classes contain instances I need to build new instances? Guess I'm confused on why I can clone instances.
Christian Graus 2-May-19 18:14pm    
If you want to clone your class, you need to simply build a new class in the interface, using the values in your object. The string class Clone method has this in the docs:

The return value is not an independent copy of this instance; it is simply another view of the same data. Use the Copy or CopyTo method to create a separate String object with the same value as this instance.

Because the Clone method simply returns the existing string instance, there is little reason to call it directly.

Strings are immutable. If you change a string, a new string is created. This means that you're making a copy, in essence. Weirdly, it means string a = b more clearly creates a new copy than string a = b.Clone()

Strings are a special case in C#. I would call the Clone method because it exists and makes your intention clear.
Sometimes it's easier to just do it yourself; see if you get some ideas from this:
using System;
using System.Collections.Generic;

namespace YourNameSpace
{
    public class Student
    {
        public List<int> Scores;

        public Student CloneOf;

        public Student(string name, params int[] scores)
        {
            CloneOf = null;

            Name = name;
            Scores = new List<int>(scores);
        }

        public string Name { get; set; }

        public bool AddScore(int score)
        {
            try
            {
                Scores.Add(score);
            }
            catch { return false; }
            return true;
        }

        public List<int> GetScores()
        {
            return Scores;
        }

        public int GetScoreAt(int index)
        {
            return (int)Scores[index];
        }

        public int GetScoreTotal()
        {
            int sum = 0;
            foreach (int score in Scores)
            {
                sum += score;
            }
            return sum;
        }

        public int GetScoreCount()
        {
            return Scores.Count;
        }

        public int GetScoreAverage()
        {
            return GetScoreTotal() / GetScoreCount();
        }

        public void DestroyScores()
        {
            Scores = new List<int>();
        }
    }

    public static class StudentExtensions
    {
        public static Student Clone(this Student stdnt)
        {
            var clone = new Student
            (
                stdnt.Name,
                stdnt.Scores.ToArray()
            )
            // note use of object initializer here
            {
                CloneOf = stdnt
            };

            return clone;
        }
        
        public static void SaveEdits(this Student stdnt, Student clone)
        {
            if (clone == null || clone.CloneOf == null || clone.CloneOf != stdnt)
            {
                throw new ArgumentException("Invalid clone argument");
            }

            if (stdnt.Scores != clone.Scores) stdnt.Scores = clone.Scores;
            if (stdnt.Name != clone.Name) stdnt.Name = clone.Name;

            // consider disposing of clone here ?
        }
    }
}
Usage example:
Student s1 =  new Student("S1", 89,92,100,78);

Student s2 = s1.Clone();

s2.Scores[2] = 87;

s1.SaveEdits(s2);
 
Share this answer
 
v2
Comments
BillWoodruff 3-May-19 6:50am    
I'd probably use a Dictionary<datetime,int> to keep track of scores, to make future editing easier, etc.
Member 14351839 3-May-19 17:53pm    
Did this succesfully clones name and list?

public class Student : ICloneable
{
public List<int> Scores = new List<int>();

public string Name
{ get; set; }

public object Clone()
{
Student obj2 = new Student();
obj2.Scores = this.Scores;
obj2.Name = this.Name;
return obj2;
}
BillWoodruff 3-May-19 22:03pm    
It is important that you construct a code experiment to test this: then single-step through it, examining the values at each line of code. Then, if it didn't work: figure out why.

You need to study the difference between objects passed as values, like integers, how the special string object is immutable, and, how other objects, like Lists are passed by reference. Understand, and understand how to use these: you will be on your way to being a competent C# programmer.

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900