user6793735
user6793735

Reputation:

Not grasping what ICollection does in C# after looking through documents

After looking for hours on Microsoft and on StackOverflow, I'm seriously not getting the reasoning on why ICollection is another class in the coding.

For example, why is there an ICollection of Book and a new list of books in the Author.cs file? I'm just not understanding it.

If it helps, I'm using Visual Studio 2017.

//Author.cs file

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace Library2.Models
{
    public class Author
    {
        private ICollection<Book> _books;

        public Author()
        {
            _books = new List<Book>();
        }

        public int Id { get; set; }

        public string FirstName { get; set; }

        public string LastName { get; set; }

        public virtual ICollection<Book> Books
        {
            get { return _books; }
            set { _books = value; }
        }
    }
}

//Books.cs file
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace Library2.Models
{
    public class Book
    {
        private ICollection<Author> _authors;

        public Book()
        {
            _authors = new List<Author>();
        }

        public int Id { get; set; }

        public string Title{get; set;}

        public string Publisher { get; set; }

        public virtual ICollection<Author> Authors
        {
            get { return _authors; }
            set { _authors = value; }
        }
    }
}

Upvotes: 3

Views: 255

Answers (2)

Basic
Basic

Reputation: 26766

The fundamental issue here is what the interfaces guarantee...

List<T> implements a number of interfaces including ICollection<T> and IEnumerable<T>.

Interfaces allow you to deal with common functionality without worrying about implementation details...

Collections can have items added/removed. Enumerables can be iterated (looped through) and Lists preserve the order of items. (Yes, there's an IList<T> too)

The method declaration you use forms part of the "signature" of the class. This method:

public virtual IEnumerable<Book> Books
{
    get { return _books; }
}

Says "I will expose books in a way that you can loop through". The class happens to do that using a List<Book>() but that's an implementation detail... Nothing outside this class ever needs to know if it's using a List, a LinkedList, an array, or something else entirely.

Upvotes: 0

StuartLC
StuartLC

Reputation: 107247

ICollection<T> is a generic interface, which is implemented by many of the common collection classes, like List<T> and Arrays. Importantly though, it does allow changes to the collection, i.e. new elements can be added or existing elements removed from it. In the example, two classes internally implement List<> of the other class, and both expose an ICollection<> property allowing access to the internal collections.

I'm fairly certain that the two classes shown (Authors and Books) are probably many to many Database Serialization entities from an ORM such as Entity Framework, and possibly even code generated, e.g. from database tables, in which case "good pricipals of Object Orientation" don't really apply, as the ORM needs to write to fields during deserialization

But for academic interest, we could consider instead that these two classes are first class "Domain" classes, we can tighten things up a bit.

At a guess, the example wants to provide an example for modelling a many-many relationship, and perhaps also demonstrate the difficulties inherent in bidirectional navigation, viz books can be written by multiple authors, and authors can write multiple books.

Anyway, I guess we can take the class hierarchy for a test drive.

First we have the chicken and egg problem - what comes first? Logically I guess the Author has to exist before books can be written, so:

var tolkien = new Author
{
     Id = 1,
     FirstName = "J. R. R.",
     LastName = "Tolkien"
     // We *could* add some books here, but in doing so, we wouldn't be able to set 
     // the linked author on the book yet, because the author hasn't been fully created
};

var lotr = new Book
{
    Id = 1,
    Title = "Lord of the Rings",
    Publisher = "Allen & Unwin",
    // We do have created the author, so we can do this ...
    Authors = new[]{ tolkien }
};

The problem here is that the links are inconsistent - lotr references tolkien, but tolkien has no books.

This needs to be fixed manually:

tolkien.Books.Add(lotr);

Since there is also bidirectional navigation between book and author, there is now a circular (infinite) reference between the two objects, as can be seen by this navigation:

Console.WriteLine(tolkien.Books.First().Authors.First().Books.First()...);

In addition to the bidirectional navigation, there are many other issues with the class hierarchy, such as:

  • It isn't a good idea to expose direct, mutable access to collections - ideally we should encapsulate change to internal fields like books and authors
  • We can enforce the 'sequence' of creation between book and Author by forcing an Author as a constructor parameter on Book - i.e. a Book must be written by an Author. This prevents a book from being in a transient 'invalid' state, where it has no author(s).
  • The creation of books itself can be performed by a Factory method - this way we can ensure an 'all at once' creation, without any transient invalid state. I've arbitrarily added the method PublishBook to Author, but this could be separated.
  • At the same time, we can ensure that the bidirectional linking between Author and Book remains intact by passing the 'this' reference of the Author to the Book.

After all this, we are left with something like:

public class Author
{
    // Restrict creation of books to just the once, at construction time
    private readonly ICollection<Book> _books;

    public Author()
    {
        _books = new List<Book>();
    }

    public int Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }

    // Take away the setter, and prevent direct external change to the collection
    public virtual IEnumerable<Book> Books
    {
        get { return _books; }
    }

    // Provide a more controlled mechanism to book creation
    public void PublishBook(int id, string title, string publisher)
    {
        _books.Add(new Book(this)
        {
            Id = id,
            Title = title,
            Publisher = publisher
        });
    }
}

public class Book
{
    private readonly ICollection<Author> _authors;

    // Book cannot be published without an author
    public Book(Author author)
    {
        _authors = new List<Author>{author};
    }

    public int Id { get; set; }
    public string Title{get; set;}
    public string Publisher { get; set; }

    // As above, don't allow direct change
    public virtual IEnumerable<Author> Authors
    {
        get { return _authors; }
    }
}   

Which hopefully results in much simpler and cleaner usage:

var tolkien = new Author
{
     Id = 1,
     FirstName = "J. R. R.",
     LastName = "Tolkien"
};

tolkien.PublishBook(1, "Lord of the Rings", "Allen & Unwin");

Upvotes: 2

Related Questions