-5

I came across the post on stackoverflow that provided solution to question by chaining two methods. The answer looked something like this:

public x DoThis()
 {
    //do something
    return this; 

 }

public x DoThat ()
{
   //do something else
   return this;

}

var x = new x ().DoThis().DoThat;

I read about chaining methods. But something does not seem to be right in this case. I created a class called Library with two different methods that return the same type, and I can access first of the methods, but not the second one. Unless, I'm doing something wrong, that solution is incorrect.

I watched tutorial on creating collection extention methods and I wanted to try use this approach. I have to admit that I do not understand everything about it yet. So I thought, I should be able to use IEnumerable<> because I'm only passing collection to this class

Here is a class:

class Library
{
    private IEnumerable<Movie> MoviesLibrary;


    public Library(IEnumerable<Movie> library)
    {
        this.MoviesLibrary = library.ToList();

    }

    public IEnumerable<Movie> FindMovie(int _movieId)
    {


        return this.MoviesLibrary.Where(movie => movie.MovieId == _movieId);


    }

    public IEnumerable<Movie> GetByYear(int _year)
    {

        return this.MoviesLibrary.Where(movie => movie.Year == _year);

    }

}

As I understand "return this" statement should return currently instantiated object. In chained method, next method should use that returned object and perform its own action.

Servy
  • 202,030
  • 26
  • 332
  • 449
tipitoe
  • 11
  • 2
  • 8

2 Answers2

0

Can you provide more context for the first code example you gave? (the one that doesn't work - for example what is X in your case? what class has the first method?).

The last example you gave

public IEnumerable<Movie> GetByYear(int _year)
{
   this.MoviesLibrary.Where(movie => movie.MovieId == _movieId);
    return this;
}

is incorrect because you return an IEnumerable, and IEnumerable doesn't have the additional method which aren't extension methods.

Extension methods indeed may help you achieve what you want. In fact, Linq is implemented as a set of extension methods to IEnumerable

See https://referencesource.microsoft.com/#System.Core/System/Linq/Enumerable.cs,577032c8811e20d3

for more info (and examples how to write extension methods).

Also you may want to read more about extension method in their formal msdn page https://msdn.microsoft.com/en-us/library/bb383977.aspx

sokohavi
  • 89
  • 5
  • I think you are missing some knowledge (that's ok) - I can give you a short answers but to understand the basic of the language you will have to delve into some documentation (c# in a nutshell is the best book I read for c#) ... no worries about that :) ... but from your edited post I think it may be better to read about the language fundamentals first .... Currently, the 'Library' example won't even compile - in order to compile it has to implement IEnumerable ... the chaining you want will work only if the return type of the methods is also Library and Library is IEnumerable. – sokohavi Apr 03 '17 at 02:29
  • I edited last method and both of them return the same type IEnumerable – tipitoe Apr 03 '17 at 03:12
0

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

tipitoe
  • 11
  • 2
  • 8