4

Suppose I have the following class:

public class Author
{
    public int ID {get; private set;}
    public string firstName {get; private set;}
    public string lastName {get; private set; }

    public Author(int id, string firstname, string lastname)
    {
        this.ID = ID;
        this.firstName = firstname;
        this.lastName = lastName;
    }

    public static Author Clone(Author clone)
    {
        Author copyAuth = new Author(clone.ID, clone.firstName, clone.lastName);
        return copyAuth;
    }
}

and

public class Book
{
    public string bookTitle {get; private set;}
    private List<Author> authors;
    private List<Author> copyofAuthors;
    public string ISBN {get; private set; }

    public Book(string bookTitle, List<Author> authors, string ISBN)
    {
        copyofAuthors = new List<Author>();
        this.bookTitle = bookTitle;
        this.ISBN = ISBN;
        //How do I create a deep copy of my authors List?

        foreach(Author copy in authors)
        {
            Author addAuthors = Author.Clone(copy);
            copyofAuthors.Add(addAuthors);
        }
    }
}

How would I create a deep copy of my List<Authors> collection? I've read other pages on StackOverFlow that suggest serialization, and suggestions that I'm unfamiliar with and seem confusing.

I followed this link to create my clone method.

Questions 1:

Is the above implementation considered a deep copy? If so, is it okay to do it this way? By that I mean, a foreach loop in the constructor copying the authors to a new list collection.

Question 2:

If I modify anything in the copyofAuthor collection, it no longer references the original collection correct? So the original collection should remain the same?

Update #1 :

    public List<Author> Authors
    {
        get
        {
            return returnAuthors(authors);
        }

    }

    private List<Author> returnAuthors(List<Author> copyList)
    {
        List<Author> getAuthors = new List<Author>();

        foreach(Author copy in copyList){
            getAuthors.Add(Author.Clone(copy));
        }

        return getAuthors;
    }

Have I implemented my getter collection properly such that when it returns the List collection, it's independent of the original collection? So any changes made from the collection returned from the getter will not be reflected in the original collection correct?

Update #2:

With ReadOnlyCollection

    public class Book
    {

        public string bookTitle {get; private set;}
        private ReadOnlyCollection<Author> authors;
        public string ISBN {get; private set; }


        public Book(string bookTitle, ReadOnlyCollection<Author> authors, string ISBN)
        {

            this.bookTitle = bookTitle;
            this.ISBN = ISBN;
            //Is it okay to do this? 
            this.authors = authors;

        }

        public List<Author> Authors
        {
            get
            {   //Create a shallow copy
                return new ReadOnlyCollection<Author>(authors);
            }

        }

    }
Community
  • 1
  • 1
  • I typically use `Activator.CreateInstance` to materialize a deeply cloned object. Sure, it uses reflection but the performance hit has been negligible to my use cases (usually wanting a fairly "shallow" array to be cloned) and the gains in code expressiveness have more than outweighed the downsides. In your case, a static clone method may satisfy your purposes.Are you looking for validation of your technique? If so, there is nothing particularly wrong with a static `Author.Clone()` method.The benefit you have from this over ICloneable is that you get an actual `Author` back, not an `object.` – K. Alan Bates Mar 07 '16 at 16:32
  • Can you describe the scenario that is leading you to believe that deep copying is a good idea? It's expensive in time and memory, and hard to get right. You might be better off with a persistent immutable collection if you want to have multiple *independent* edits to an original list. Particularly given that authors are already immutable; why would you want to clone one in the first place? – Eric Lippert Mar 07 '16 at 16:35
  • @K.AlanBates - Many thanks, I was looking for validation for this method, and could you check my update and please advise? I implemented a getter and wanted to know if I did it properly. –  Mar 07 '16 at 16:35
  • @EricLippert - Could you show me how to implement an persistent immutable collection? –  Mar 07 '16 at 16:36
  • @Svetlana ...my advice to you right now is that you have Eric Lippert interested in your post. Ignore anything I say and listen to him lol – K. Alan Bates Mar 07 '16 at 16:36
  • @EricLippert - I don't want any users to make copies to the original collection, i.e any changes they make is on a copy of the collection, not the one stored in the class –  Mar 07 '16 at 16:37
  • I certainly could show you how to implement a persistent immutable collection, and that would be educational for you I'm sure. But if you have a business problem to solve then I would suggest that instead of implementing your own, you simply use the immutable collection library from Microsoft. – Eric Lippert Mar 07 '16 at 16:38
  • OK, you don't want users to change the collection. Then either hand them a ReadOnlyCollection wrapper around your list, or call ToImmutable on the list. Why do you want to clone **the authors**? That is the part that I find very confusing. – Eric Lippert Mar 07 '16 at 16:39
  • @EricLippert - So, ReadOnlyCollection? would that work, if you could, can you show me how to use it in my class, i.e implement it in the constructor, and provide a getter. –  Mar 07 '16 at 16:39
  • @EricLippert - Is my clone method in my Authors unnecessary, given that it's immutable? –  Mar 07 '16 at 16:40
  • 1
    Start by reading the documentation. – Eric Lippert Mar 07 '16 at 16:40
  • Well, you're the one who is cloning the authors. I'm asking you why you think that's a good idea. What benefit does that get you? – Eric Lippert Mar 07 '16 at 16:40
  • I have put the problem is I still have doubts and not sure how to proceed. –  Mar 07 '16 at 16:41
  • This is a question-and-answer site. If you have a question about how to use a class in the framework, post a question! – Eric Lippert Mar 07 '16 at 16:41
  • @EricLippert - To be quite honest, I have no idea why I'm doing that, I read about deep copy and shallow copy, and was confused how to do it when I had a list as part of my constructor. –  Mar 07 '16 at 16:42
  • @EricLippert - I just posted one in the comments, could you show me how to properly implement ReadOnlyCollection in the constructor and have a getter. –  Mar 07 '16 at 16:43
  • 1
    If you are writing code and you have no idea why you are writing it then you started writing code too early today. Write code *after* you know why you are writing it. Don't worry about deep copies vs shallow copies; this is a distinction that rarely matters. Think about *what operations should the consumer of your method be able to perform*. You are absolutely right to be thinking about whether those consumers can mutate your collection! If that is your concern then *don't let them mutate your collection*. Hand them an immutable collection. – Eric Lippert Mar 07 '16 at 16:44
  • So a ReadOnlyCollection, means that if they want to add a new author they have to create a new Book object add the new author, right? They can't modify it from the getter correct? –  Mar 07 '16 at 16:46
  • @EricLippert - Let me update with what I have in mind, and can you please advise, it will only take a few minutes. –  Mar 07 '16 at 16:47
  • @EricLippert - Just for own understanding, deep-copy is used for classes that aren't immutable correct? If it was immutable having a clone method would be unnecessary, as the user would have the create a new object to update any attributes, correct? –  Mar 07 '16 at 16:50
  • @EricLippert: check update #2 –  Mar 07 '16 at 16:52
  • @Svetlana: Correct. If you want to have two independent edits to one mutable thing then you need *two* mutable things, which means copying the whole thing. – Eric Lippert Mar 07 '16 at 16:56

3 Answers3

5

It's getting too hard to sort this out in the comments.

  • Remove the Clone method from your Author class. It is useless.

In your book class, you have two problems to solve.

  • The constructor takes a list of authors, but the caller who passed it in might change it. If we just copy the reference, then the caller can alter accidentally the list held by the book.

  • The book gives back a list of authors. If a caller adds something to that list then again, they've mutated the book.

You can solve both problems with an immutable collection. Download the immutable collections library using NuGet if you haven't got it already.

using System.Collections.Immutable;
...
public class Book
{
  public string bookTitle {get; private set;}
  private ImmutableList<Author> authors;
  public IReadOnlyList<Author> Authors { get { return authors; } }
  public string ISBN {get; private set; }

  public Book(string bookTitle, IEnumerable<Author> authors, string ISBN)
  {
    this.authors = ImmutableList<Author>.Empty.AddRange(authors);
    this.bookTitle = bookTitle;
    this.ISBN = ISBN;
  }
}

There. Now you make a copy of the sequence of authors, so if the caller changes that sequence, no worries, you have a copy. And you hand out an IReadOnlyList that is implemented by an immutable collection, so no one can change it.

Couple more things. You ask "is this right?"

public class Book
{
    private ReadOnlyCollection<Author> authors;
    public Book(ReadOnlyCollection<Author> authors)
    {
        //Is it okay to do this? 
        this.authors = authors;
    }

    public List<Author> Authors
    {
        get
        {   //Create a shallow copy
            return new ReadOnlyCollection<Author>(authors);
        }
    }

(Extraneous stuff removed).

No, that is not quite right, for a couple reasons. First, the read only collection is just a wrapper around a mutable collection. You are still in the situation where the caller controls the underlying collection, and therefore can change it.

Second, the typing doesn't quite work out; you can't convert a ReadOnlyCollection to a List.

This is confusing I know. There is a subtle distinction here. A read only collection is just that: you can only read it. It doesn't mean that someone else cannot write it! Such a collection is still mutable, it is just not mutable by you. An immutable collection is truly immutable; no one can change it.

Next: you are doing very well by making the author and the book both immutable. But what if you want to change it? As you note, to change an immutable book means making a new book. But you already have an old book; how can you do that efficiently? The common pattern is:

public class Book
{
  public string Title {get; private set;}
  private ImmutableList<Author> authors;
  public IReadOnlyList<Author> Authors { get { return authors; } }
  public string ISBN {get; private set; }

  public Book(string title, IEnumerable<Author> authors, string ISBN) : this(
    title, 
    ImmutableList<Author>.Empty.AddRange(authors),
    ISBN) {}

  public Book(string title, ImmutableList<Authors> authors, string ISBN) 
  {
    this.Title = title;
    this.Authors = authors;
    this.ISBN = ISBN;
  }
  public Book WithTitle(string newTitle)
  {
    return new Book(newTitle, authors, ISBN); 
  }
  public Book WithISBN(string newISBN)
  {
    return new Book(Title, authors, newISBN);
  }
  public Book WithAuthor(Author author)
  {
    return new Book(Title, authors.Add(author), ISBN);
  }
  public static readonly Empty = new Book("", ImmutableList<Author>.Empty, "");
}

Now you can do this:

Book tlotr = Book.Empty.WithAuthor("JRRT").WithTitle("The Lord Of The Rings");

And so on.

Eric Lippert
  • 647,829
  • 179
  • 1,238
  • 2,067
  • Many thanks for this! Just for my understanding. When you implement a clone method, only do so for mutable objects correct? Immutable means that they can't change the state once it's created, so they would have to create a new Book object to update anything, correct? –  Mar 07 '16 at 16:56
  • @Svetlana: Correct. Let me show you a common pattern for that. – Eric Lippert Mar 07 '16 at 16:56
  • Does Java also have ImmutableList? –  Mar 07 '16 at 17:11
  • 1
    @Svetlana: I have written exactly zero lines of production Java code in my life. I have no idea. I suggest you start looking on the Internet. – Eric Lippert Mar 07 '16 at 17:12
  • Just more question, lets say if I used a List so, List authorList...made it private, with no getter, just set it in the constructor, could the user still modify the List object after it was set? –  Mar 07 '16 at 17:27
  • @Svetlana: Make sure you are distinguishing between the *reference* to the list and the *referred to* list. If a reference is stored in a private variable, etc, then only the code that can access that variable somehow can change the variable. But anyone who has *a reference* to a list can change the contents of the list. – Eric Lippert Mar 07 '16 at 19:05
  • Many thanks, for the code and effort to explain it to me! –  Mar 07 '16 at 19:49
  • The whole point of making the List collection and to a larger extent the object, immutable, is for thread safety, correct? –  Mar 07 '16 at 20:02
  • @Svetlana: No. The whole point of making things immutable is to (1) prevent bugs whereby mutable state is modified by code that has no business modifying it, and (2) to make programs easier to reason about; things that change are hard to understand, and (3) to enable application of mathematical formalisms to code, and (4) to enable performance optimizations such as memoization and (5) to take advantage of memory savings inherent in persistent collections, and ... and I could go on for some time. Thread safety is nice to have but it is low on my list of reasons to prefer immutability. – Eric Lippert Mar 07 '16 at 23:14
0

If you have many properties it ll be boring to write this:

new Author(clone.ID, clone.firstName, clone.lastName...);

Check this little exemple of how u can implement ICloneable interface :

This exemple comes from : http://csharp.2000things.com/2010/11/07/143-an-example-of-implementing-icloneable-for-deep-copies/

public class Person : ICloneable
{
    public string LastName { get; set; }
    public string FirstName { get; set; }
    public Address PersonAddress { get; set; }

    public object Clone()
    {
        Person newPerson = (Person)this.MemberwiseClone();
        newPerson.PersonAddress = (Address)this.PersonAddress.Clone();

        return newPerson;
    }
}

public class Address : ICloneable
{
    public int HouseNumber { get; set; }
    public string StreetName { get; set; }

    public object Clone()
    {
        return this.MemberwiseClone();
    }
}

Person herClone = (Person)emilyBronte.Clone();
Quentin Roger
  • 6,410
  • 2
  • 23
  • 36
0

Question 1: Yes this performs a deep copy. I recommend using the ICloneable interrface instead of this static function as it is standard.

Question 2: Yes the original collection will not change as you are using a new collection object.

Question 3: If you return the collection in the getter then someone can transform it. Either you return a new copy of the collection each time someone wants to get it or you wrap it in a container that would have a private readonly collection and does not expose the collection (but can be enumerable).

In your case not sure if you want the Author to be independent though. Given that they already can only be set privately, it might be useful to share updates on them. The only thing you need to keep independent is the collection. So maybe no need ot clone the Authors.

Jonathan Nappee
  • 430
  • 2
  • 11
  • the link I posted said ICloneable should be depreciated, and not to use it. –  Mar 07 '16 at 15:32
  • Could you show me how to implement a getter that copies the collection? Could I have a private method that loops though, like I have in my constructor? –  Mar 07 '16 at 15:37
  • If I update my question, could you take a look and advise? –  Mar 07 '16 at 15:58