C# Method Chaining



25 February 2019 ·  11 min read

C# Method chaining is something that you’ll most likely be consuming already, without necessarily appreciating how it works under the hood.

The basic concept of method chaining is that context flows from one method to the next - hence the term ‘chaining’. This style of programming can make code easier to read.

If you’re used to working with something like LINQ, you’ll be familiar with this idea. For example:-

var list = myCollection
  .Where(...)
  .OrderBy(...)
  .Take(5);

Method chaining is not best suited to all scenarios, but it can be useful when you are acting on a common set of data and want to apply a sequence of processing steps.

By exposing each process as a method, you can cherry-pick which of these processes to apply in your calling code.



I’ve heard of “Fluent Interfaces” - is this the same thing?

Not quite, but they are related.

Method chaining is a forebearer of “Fluent Interfaces” (a term coined by Martin Fowler in 2005).

Fluent Interfaces are commonly used for things such as factory builders and, for code such as LINQ where you can “layer in” different aspects of a query.

Method chaining typically acts upon the same set of simple data, whereas a Fluent Interface is more typically used as a way to modify different aspects of a more complex object.

A good example of this would be the configuration builder in the Startup class of a .NETCore Web application. In the following example, we can see that everything is clearly related to the process of starting up an application, but we can’t say that the ‘base path’ and ‘appsettings’ are the same set of data. Consider the following fragment of code:-

...
public Startup(IHostingEnvironment env)
{
  var builder = new ConfigurationBuilder()
      .SetBasePath(env.ContentRootPath)
      .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
      .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
      .AddEnvironmentVariables();

...



Let’s walk through an example…

We’re going to write a small console application to modify a list of words. We want our program to do the following:

  • To filter out all words that are not at least 3 characters long.
  • To filter out all words that don’t contain the character “c”

The complete code for such a program might look like this:

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

namespace MethodChaining
{
  class Program
  {
      static void Main(string[] args)
      {
          var listWords = new List<string>(new string[] { "this", "is", "a", "sample", "list", "of", "words", "that", "demonstrates", "some", "code" });

          listWords = listWords.Where(w => w.Length >= 3).ToList();
          listWords = listWords.Where(word => word.ToLower().Contains("c")).ToList();
        
          Console.WriteLine(String.Join(", ", listWords.ToArray())); // Print array to console, separating each word with a comma
      }
  }
}

Ok, I’ll be the first to admit that in this literal example the code is very simple. Arguably, as presented above, the code is already concise and probably would not justify refactoring.

However, as with any code we write, we should always be open to the possibility that it will be added to over time. As such, we should aspire to write our code in such a way that helps support maintainability and future expansion.

Regardless of the actual complexity, it’s still a good idea to refactor our business logic into a separate method(s). We would want to do this for a couple of reasons - we’ve already suggested maintainability, but we could also consider readability and testability.

Such a piece of code could look like this:

class Program
{
   static void Main(string[] args)
   {
       var listWords = new List<string>(new string[] { "this", "is", "a", "sample", "list", "of", "words", "that", "demonstrates", "some", "code" });

       listWords = FilterOutShortWords(listWords);
       listWords = FilterOnlyWordsContainingLetterC(listWords);

       Console.WriteLine(String.Join(", ", listWords.ToArray()));
   }

   private static List<string> FilterOutShortWords(List<string> listWords)
   {
       return listWords.Where(w => w.Length >= 3).ToList();
   }

   private static List<string> FilterOnlyWordsContainingLetterC(List<string> listWords)
   {
       return listWords.Where(word => word.ToLower().Contains("c")).ToList();
   }
}


Refactor into a separate class

In the above example, each method accepts a parameter and returns a value. We have started to pass data around between methods, by using arguments.

The value that is returned from a method, needs to be assigned back “somewhere” on each invocation. In this example, that happens to be the listWords variable in the Main method.

Whilst we have decomposed our business logic, the addition of the new methods into the Program class isn’t ideal. This is because the purpose of the Program class now isn’t clear. Technically, it’s doing too many things (it’s orchestrating the program and being used to define business logic).

If we intend to follow best practice, we should aim to keep the purpose of each class as focused as possible. By this, I am eluding to the “SOLID Principles” and specifically the “Single Responsibility Principle”)

To clarify, we’re talking about concepts here. In the literal context of this tiny example, you could reasonably argue that there isn’t a problem with the code and that we’re over-complicating things. However, if you were to keep adding more and more code into the Main method, we can see how that could evolve into a problem.

As such, the above code could be refactored into a separate class. When we do this, our program now looks something like this:

namespace MethodChaining
{
  class Program
  {
      static void Main(string[] args)
      {
          var filterLogic = new FilterLogic();
          var listWords = new List<string>(new string[] { "this", "is", "a", "sample", "list", "of", "words", "that", "demonstrates", "some", "code" });

          listWords = filterLogic.FilterOutShortWords(listWords);
          listWords = filterLogic.FilterOnlyWordsContainingLetterC(listWords);

          Console.WriteLine(String.Join(", ", listWords.ToArray()));
      }
  }


  public class FilterLogic
  {
      public List<string> FilterOutShortWords(List<string> listWords)
      {
          return listWords.Where(w => w.Length >= 3).ToList();
      }

      public List<string> FilterOnlyWordsContainingLetterC(List<string> listWords)
      {
          return listWords.Where(word => word.ToLower().Contains("c")).ToList();
      }
  }
}


Add some more business logic complexity

Now that we have refactored our code into a separate class, introducing additional business logic as additional methods would be trivial.

To demonstrate that point, let’s add in some extra functionality to our example. Purely for the sake of brevity in our demonstration, let’s pretend that we’re going to be adding in some arbitrarily complicated code. Let’s just say that we don’t need to be specific about what that code should do, nor how it is written - but let’s just say that it would represent many, many lines of code and would be something that we clearly want to encapsulate as a separate method.

Let’s add in that code and show you how we’ll represent it in our example. Our FilterLogic class will now look like this:-

public class FilterLogic
{
  public List<string> FilterOutShortWords(List<string> listWords)
  {
      return listWords.Where(w => w.Length >= 3).ToList();
  }

  public List<string> FilterOnlyWordsContainingLetterC(List<string> listWords)
  {
      return listWords.Where(word => word.ToLower().Contains("c")).ToList();
  }
   public List<string> FilterArbitraryComplexItems(List<string> listWords)
  {
      /* Some arbitrarily complex logic that represents many lines of code */
      return listWords;
  }

  public List<string> FilterSomeMoreArbitraryComplexItems(List<string> listWords)
  {
      /* Some additonal other arbitrarily complex logic that represents many lines of code */
      return listWords;
  }
}

What isn’t so great about this change, is that we’re now having to write a bit too much to invoke our new code in the Main method:-

...
  listWords = filterLogic.FilterOutShortWords(listWords);
  listWords = filterLogic.FilterOnlyWordsContainingLetterC(listWords);
  listWords = filterLogic.FilterArbitraryComplexItems(listWords);
  listWords = filterLogic.FilterSomeMoreArbitraryComplexItems(listWords);
...

We could refactor the above so that the output of one method is passed directly to the next. This would technically reduce the amount of code slightly:-

...
  listWords = filterLogic.FilterSomeMoreArbitraryComplexItems(filterLogic.FilterArbitraryComplexItems(filterLogic.FilterOnlyWordsContainingLetterC(filterLogic.FilterOutShortWords(listWords)));
...

You would be absolutely right in saying that the above code is pretty horrible and is definitely very difficult to read now!

Legibility aside, we can see that data is starting to flow from one method to the next, more fluidly.


Refactor, using private member

The problem now is that we are passing data around as method arguments, adding length to the code in both the Main method and the FilterLogic class.

One way to address this issue could be to encapsulate the data as a private member of the FilterLogic class. Instead of using method parameters, each method refers to the internal variable.

Our code would now start to look something like this (showing just one method for brevity):-

public class FilterLogic
{
  private List<string> _listWords  = new List<string>();

  public void FilterOutShortWords()
  {
      _listWords = _listWords.Where(w => w.Length >= 3).ToList();
  }

  ...


Expose the private member*

However, the change we’ve introduced now means that the various methods no longer return a value.

This means that in order to get data out of an instance of the FilterLogic class, we would need some way to ‘get’ and ‘set’ the value of the private member _listWord.

You might think that just adding in a public ‘getter/setter’ (such as public List<string> ListWords {get; set;} may be the solution here. However, doing so would effectively mean that we need four separate steps in our calling code, that cannot be chained together, like this:-

  • One line to ‘new-up’ the Filter Logic class
  • One line to ‘set’ the value of the object; e.g. filterLogic.ListWords = new string[] { "this", "that};
  • One line to chain together multiple process steps; e.g. filterLogic.FilterOutShortWords().FilterOnlyWordsContainingC();
  • One line to ‘get’ the value of the object; e.g. var result = filterLogic.ListWords

Instead, let’s use a constructor to add data to the object.

Similarly, to retrieve data from the class, we can create a very basic public method that accesses and returns the private member value. As such a method is always expected to be called last in the chain, we refer to it as a Terminating Method.

If you use LINQ, you’ll be familiar with the usage pattern. In LINQ, typically methods are chained together to construct the query. However, these methods alone do not return an the data, they produce an IQueriable object. To yield an actual data result, you would need to add something like a .ToList() as a chain terminator.

Adding the constructor to the class:

...
public FilterLogic(List<string> listWords)
{
  _listWords = listWords;
}
...

Adding the terminator to the class:

...
public List<string> GetWords()
{
  return _listWords;
}
...

So now our class looks like this:

public class FilterLogic
{

 private List<string> _listWords  = new List<string>();

  public FilterLogic(List<string> listWords)
  {
     _listWords = listWords;
  }

 public List<string> GetWords()
 {
    return _listWords;
 }

  public List<string> FilterOutShortWords(List<string> listWords)
  {
      return listWords.Where(w => w.Length >= 3).ToList();
  }

  public List<string> FilterOnlyWordsContainingLetterC(List<string> listWords)
  {
      return listWords.Where(word => word.ToLower().Contains("c")).ToList();
  }
   public List<string> FilterArbitraryComplexItems(List<string> listWords)
  {
      /* Some arbitrarily complex logic that represents many lines of code */
      return listWords;
  }

  public List<string> FilterSomeMoreArbitraryComplexItems(List<string> listWords)
  {
      /* Some additonal other arbitrarily complex logic that represents many lines of code */
      return listWords;
  }
}



image showing bottles of hot sauce

The method chain magic sauce

Method Chaining works by each method returning an instance of itself.

Therefore, our FilterLogic methods need to look like the following example, with the return this returning the instance of itself.

  public FilterLogic FilterOutShortWords()
  {
      _listWords = _listWords.Where(w => w.Length >= 3).ToList();
      return this;
  }

With this approach applied throughout the FilterLogic class, our calling code, in the Main method for example, could look something like the following partial fragment. We can now see that the methods are chaining together:-


... FilterOutShortWords().FilterOnlyWordsContainingLetterC().FilterArbitraryComplexItems().FilterSomeMoreArbitraryComplexItems();



The end result

Let’s see how the entire refactored code looks now.

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

namespace MethodChaining
{
  class Program
  {
      static void Main(string[] args)
      {
          var listWords = new List<string>(new string[] { "this", "is", "a", "sample", "list", "of", "words", "that", "demonstrates", "some", "code" });

          List<string> filteredWords = new FilterLogic(listWords)
              .FilterOutShortWords()
              .FilterOnlyWordsContainingLetterC()
              .FilterArbitraryComplexItems()
              .FilterSomeMoreArbitraryComplexItems()
              .GetWords();

          Console.WriteLine(String.Join(", ", filteredWords.ToArray()));
      }
  }


  public class FilterLogic
  {
      private List<string> _listWords  = new List<string>();

      //Constructor
      public FilterLogic(List<string> listWords)
      {
          _listWords = listWords;
      }

      //Chain Terminator
      public List<string> GetWords()
      {
          return _listWords;
      }

      public FilterLogic FilterOutShortWords()
      {
          _listWords = _listWords.Where(w => w.Length >= 3).ToList();
          return this;
      }

      public FilterLogic FilterOnlyWordsContainingLetterC()
      {
          _listWords = _listWords.Where(word => word.ToLower().Contains("c")).ToList();
          return this;
      }

      public FilterLogic FilterArbitraryComplexItems()
      {
          /* ... some arbitrarily complicated code ... */
          return this;
      }

      public FilterLogic FilterSomeMoreArbitraryComplexItems()
      {
          /* ... some arbitrarily complicated code ... */
          return this;
      }
  }
}



In closing

Method Chaining in C# is a somewhat niche solution. However, in the right context, it can help by making your code more readable.

I should stress that there are other coding approaches, to address the same sort of problem, that may be more appropriate:

An example of such a way of coding can be found in my article on Implementing the Open/Closed Principle in C#. Please note that at the very end of that article, I have included a code listing which addresses the example found in this article, except written in an extensible way.



Further reading:



(*) Now that I’ve noticed it, I really should rename that section heading … :-)




Archives

2019 (21)