Shallow and deep copying objects
Ημερομηνία: 27/02/2016
Δεν έχετε συμπληρώσει όλα τα απαιτούμενα στοιχεία
Το email που δώσατε δεν είναι σωστό
Working as a developer I have created quite a lot of objects. Most of them were unique. There were some of them, however, that were created by copying already existing objects. C# provides us with a few techniques to accomplish this, keeping in mind whether we want to create a copy that keeps all data the prototype had, or how to deal with the referenced types contained within that object.
Shallow and deep copies
A way to understand things better is to create a class called Book. What we're going to do, is try to copy Book objects. Here's the class.
public class Book
{
public string Name { get; set; }
public double Price { get; set; }
public Writer Author { get; set; }
public Book(string name, double price, Writer author)
{
Name = name;
Price = price;
Author = author;
}
}
public class Writer
{
public string Name { get; set; }
}
Book class contains three parameters: Name, Price and Author. Author is a Writer; a class that contains only one parameter; string Name.
Book book = new Book("The Gods Themselves", 7.5, new Writer { Name = "Isaac Asimov" });
We have just created a Book object.
The most straightforward way to create a copy is a new constructor that gets the old object and creates the copy.
public Book(Book book)
{
Name = book.Name;
Price = book.Price;
Author = book.Author;
}
Book bookCopy = new Book(book);
And this is our brand new copy. Let's change something shall we?
bookCopy.Name = "Out Of The Silent Planet";
After that command, book's name is The Gods Themselves and bookCopy's name is Out Of The Silent Planet. However since Out Of The Silent Planet is written by C.S.Lewis we should also change the author name.
bookCopy.Author.Name = "C. S. Lewis";
Done. So, right now we got two books. Asimov's The Gods Themselves and Lewis' Out Of The Silent Planet. Actually... no we don't. At the moment both books seem to be written by Lewis. Why is that? That is because when we created our copy, we created a fresh copy of Name, Price and the reference to Asimov. Not the Writer itself. As a result changing the referenced Writer from our copy, we change the value the original object is pointing to as well.
A copy that works this way is called a shallow copy. Could we change the constructor somehow so it creates a copy of the author instead of a copy to the reference? Yes, we can. All we need to do is:
public Book(Book book)
{
Name = book.Name;
Price = book.Price;
//Deep copy
Author = new Writer { Name = book.Name};
}
Now, if we run the same code as above, Asimov will stand right where we put him and both books will have their respective authors. A copy where all values are copied from the scratch is called a deep copy.
Now that we have come in terms with what a shallow and a deep copy is, let's focus on methods we can use to create them.
Using MemberwiseClone
Using a constructor we can create a copy of an object whether this is a shallow or a deep one or something in between. We can also add extra features to it the way we like. Of course changing the values compared to the ones the original object had will not create an exact copy but, anyway, the point is that using a constructor you are free to do whatever you want.
MemberwiseClone is .NET's way of creating a shallow copy. It will copy all non reference values and copy the reference of the reference types. Let's create the Copy method for our Book class.
public Book Copy()
{
//Shallow copy
return (Book)this.MemberwiseClone();
}
MemberwiseClone will create a shallow copy. We can add extra code to create a deep copy.
public Book DeepCopy()
{
//Deep copy
Book copy = (Book)this.MemberwiseClone();
copy.Author = new Writer { Name = this.Author.Name };
return copy;
}
The IClonable interface
IClonable is an interface created in order to be implemented on classes that are expected to copy themselves. It requires the Clone method. Here's how the Book class would look like using the IClonable interface.
public class BookClone : ICloneable
{
public string Name { get; set; }
public double Price { get; set; }
public Writer Author { get; set; }
public BookClone(string name, double price, Writer author)
{
Name = name;
Price = price;
Author = author;
}
public object Clone()
{
return this.MemberwiseClone();
}
}
This is an example of how we can use the IClonable interface
BookClone book = new BookClone("The Gods Themselves", 7.5, new Writer { Name = "Isaac Asimov" });
BookClone bookCopy = (BookClone)book.Clone();
So, if a class implements the IClonable interface then we may presume that it supports objects copying.
Since ICloneable is nothing but an interface, all that it says is that you have to implement the Clone method. However it does not tell you what the Clone method does. Clone could be implemented to create a shallow copy or a deep copy for example. People argue that the IClonable and the Clone method are more of a trouble than help as a developer will have to check what the Clone implementation actually does before using it.
In other words a person might prefer creating his own e.g. ICopyable interface which contains the CopyShallow and CopyDeep methods and make things clearer for everyone using that code.
Serialization
Searialization is the process of converting an object into a stream of bytes. You may want to do that in order to store that info in memory or in a file or for any other reason you like. Deserialization is the reverse procedure; getting a stream of bytes and turning it back into an object.
By serializing and deserializing an object we can easily create a deep copy. Check out the following method.
public Book DeepCopyUsingSerialization()
{
using (MemoryStream memoryStream = new MemoryStream())
{
IFormatter formatter = new BinaryFormatter();
formatter.Serialize(memoryStream, this);
memoryStream.Seek(0, SeekOrigin.Begin);
return formatter.Deserialize(memoryStream) as Book;
}
}
To make that code build, you may want to use the following libraries.
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
Oh, and don't forget to declare your classes as [Serializable].
All we are doing is what we described earlier. We serialize the object and then create a new object by deserializing the stream created. There are many ways to accomplish object serialization in .NET. The example I've written is just one of them.
Keep in mind that using serialization might be a slower method to choose than the ones mentioned before.
Apart from the methods described, you may also use reflection to copy objects. I am planning on writing an article concerning reflection later on, so apart from mentioning it, I am not going to write more about it.
There is no perfect way to copy an object. The ones we talked about are the most commonly used. You may choose the best one, depending on your case or create your own custom method.
Summary
When it comes to copying objects, there are two categories; shallow and deep copies. Shallow copies will hold the same references to referenced individual objects and copy all other values, while deep copies will create new instances for these objects as well. There are many ways to copy an object either shallow, or deep. Using constructors, the MemberwiseClone method, serialization and reflection being among them.
Πίσω στο BlogΠροηγούμενοΕπόμενο