This is a solution to my own question. I have to admit that it is not clear from the beginning what I was trying to accomplish, which is evident in the edit of the initial post. The first code example is answer to similar question posted by someone else on this site. It is very general in nature, however you can clearly see that this person is trying to advocate that it is possible to chain methods by returning the same object type. I wanted to accomplish something similar after watching tutorial called “Making Your C# Code More Object-Oriented” by Zoran Horvat. This tutorial is available on Pluralsight. His example is utilizing interfaces and extension methods in chapter 4 and 5. The idea for this solution is somewhat similar, but I wanted to have this functionality contained in a single class.
I think entire confusion is related to the type of the object that method needs to return in order to provide chaining functionality. Let’s take a look at some simple string example
someString.ToUpper.ToLower.Trim
The first thing that comes to our mind is that string is passed from one method to another method and at each step it is being modified by that method. Therefore we would have similar situation when working with collections.
movies.GetByYear(1999).GetByGroup(1).GetByGenre("Action")
In this case we start with some List that is being passed through this chain of methods. It is also very likely that we think that all methods in this chain operate on the same List. After all, string property from previous example is shared among all methods even though it is being modified. That’s not what actually happens with this collection of movies. Each method works with collection that has different size. It looks like GetByYear() and GetByGroup() are methods that work with the same list of movies, they are in fact separate Library objects that have completely different lists.
I would like to thank Sokohavi who left comment about returning Library object. He also suggests making Library object IEnumerable. Unfortunately, if you are thinking that method should return IEnumerable, than you are on the wrong path. Technically speaking Library is a List, but the list that holds Movie objects is set to private and is not visible to other objects. Therefore there is nothing to iterate through. Library object has only couple methods, and if you select one of them you will lose access to other methods in the same class. Therefore method must return Library object in order to gain access to all methods in the same class, and the list that stores Movie object must be IEnumerable. There is one drawback to this approach. You cannot load data to this list inside Library constructor. Instead data is passed as Parameter . Now you have objects that have different lists of movies and the way they communicate with each other is through their constructors.
Below we have Repository class that loads individual items into a list through its constructor. Library class defines methods that will provide filtering of the list that is being passed to it. You can also create another layer of abstraction that would use that functionality.
public class Movie
{
public string Title { get; set; }
public int Year { get; set; }
public int GroupId { get; set; }
public string Genre { get; set; }
}
public class Repository
{
private List<Movie> localDb;
public Repository()
{
localDb = new List<Movie>();
}
public IEnumerable<Movie> GetAllMovies()
{
localDb = new List<Movie>();
var movie1 = new Movie() { Title = "Movie1", Year = 2000, GroupId = 1, Genre = "Action" };
var movie2 = new Movie() { Title = "Movie2", Year = 1999, GroupId = 1, Genre = "Drama" };
var movie3 = new Movie() { Title = "Movie3", Year = 2000, GroupId = 1, Genre = "Comedy" };
var movie4 = new Movie() { Title = "Movie4", Year = 2000, GroupId = 2, Genre = "Action" };
var movie5 = new Movie() { Title = "Movie5", Year = 1999, GroupId = 2, Genre = "Drama" };
var movie6 = new Movie() { Title = "Movie6", Year = 1999, GroupId = 2, Genre = "Drama" };
var movie7 = new Movie() { Title = "Movie7", Year = 1999, GroupId = 2, Genre = "Horror" };
localDb.Add(movie1);
localDb.Add(movie2);
localDb.Add(movie3);
localDb.Add(movie4);
localDb.Add(movie5);
localDb.Add(movie6);
localDb.Add(movie7);
return localDb;
}
}
public class Library
{
private IEnumerable<Movie> MoviesLibrary;
public Library(IEnumerable<Movie> movies)
{
this.MoviesLibrary = movies.ToList();
}
public Library GetByYear(int year)
{
return new Library(this.MoviesLibrary.Where(movie => movie.Year == year));
}
public Library GetById(int id)
{
return new Library(this.MoviesLibrary.Where(movie => movie.GroupId == id));
}
public IEnumerable<Movie> GetByGenre(string genre)
{
return this.MoviesLibrary.Where(movie => movie.Genre == genre);
}
public void Display()
{
foreach (var movie in this.MoviesLibrary)
{
Console.WriteLine("Title: {0} , Year {1}, Group: {2}, Genre: {3}", movie.Title,movie.Year,movie.GroupId, movie.Genre);
}
}
}
How to use these classes:
var repository = new Repository();
var listOfMovies = repository.GetAllMovies();
var movies = new Library(listOfMovies);
var selectedMovies1 = movies.GetByYear(2000).GetById(1).GetByGenre("Action");
var selectedMovies2 = movies.GetByYear(2000).GetById(2);
foreach (var movie in selectedMovies1)
{
Console.WriteLine("Selected 1 - Title: {0} , Year {1}, Group: {2}, Genre: {3}", movie.Title,movie.Year,movie.GroupId, movie.Genre);
}
selectedMovies2.Display();
Output:
Selected 1 - Title: Movie1 , Year 2000, Group: 1, Genre: Action
Title: Movie4 , Year 2000, Group: 2, Genre: Action