DEV Community

Cover image for Introducing: C# Pipe Extensions!
Winston Puckett
Winston Puckett Subscriber

Posted on • Edited on

Introducing: C# Pipe Extensions!

tl;dr - I've recreated the forward pipe operator from F# to be used in C#. Visit the NuGet page to start using it yourself.

Why does this package exist?

Local variables make clean code look cluttered. It's hard to see the flow of operation when you need to refer back to the variables being used. For instance, let's look at this code:

// Example One
public void ExecuteUserFlow(Input input)
{
    var model = Query(input);
    Validate(model);
    var outputModel = Transform(model);
    Submit(outputModel);
}
Enter fullscreen mode Exit fullscreen mode

What you want to see is the flow of operations:

  1. Query
  2. Validate
  3. Transform
  4. Submit

Because you know the flow of operations, every local variable is noise.

You could deal with this by calling the functions from the last statement inward...

// Example Two
public void ExecuteUserFlow(Input input)
{
    Submit(
        Transform(
            Validate(
                Query(
                    input))));
}
Enter fullscreen mode Exit fullscreen mode

...but I think you'll agree that this decreases the readability significantly. At the end of the day, it would be better to create a bit of noise by using local variables than to use the confusing syntax above.

But what if there was a third option?

Learning from functional languages

Functional languages often lend themselves well to a compact syntax. I fell in love with F# because of its Forward Pipe Operator (|>).

The first example above is the preferred way to write code because it allows a developer to read the statement from top to bottom. The second is better because it allows you to not use any local variables. A forward pipe operator allows you to read from top to bottom without local variables by passing the result of the last function as an input to the next.

Here is an example of the forward pipe operator in F#:

input
  |> Query
  |> Validate
  |> Transform
  |> Submit
Enter fullscreen mode Exit fullscreen mode

There is also a similar operator in Clojure, but I don't know Clojure that well, so I'll just leave a link to its version of the forward pipe operator, ->>.

This sort of syntax, which gives you a top/down, compact way to specify sequence is one of my favorite ways to make code more readable. Having a forward pipe operator in C# would be amazing.

Introducing WinstonPuckett.PipeExtensions!

I've made a package to do just that! It's free, open source, and available on NuGet.

Here are the major features:

  • Uses extension methods.
  • Works on any object.
  • You can chain async/non-async methods in any order without any hassle.

Examples

Simple example:

public void ExecuteUserFlow(Input input)
{
    input
      .Pipe(Query)
      .Pipe(Validate)
      .Pipe(Transform)
      .Pipe(Submit);
}
Enter fullscreen mode Exit fullscreen mode

Use an async method:

public async Task ExecuteUserFlow(Input input)
{
    await input
      .Pipe(Query)
      .Pipe(Validate)
      .Pipe(Transform)
      .PipeAsync(SubmitAsync); // Notice this async method
}
Enter fullscreen mode Exit fullscreen mode

Mix async and non-async methods:

public async Task ExecuteUserFlow(Input input)
{
    await input
        .Pipe(Query)
        .PipeAsync(ValidateAsync)
        .PipeAsync(Transform) 
        .PipeAsync(SubmitAsync);

    // Notice that transform is not 
    // async, but Pipe awaits a task 
    // from ValidateAsync, so PipeAsync 
    // is required.

    // You must always use PipeAsync
    // after your first async method.
}
Enter fullscreen mode Exit fullscreen mode

How it works inside

If you're curious, the code to implement this is incredibly simple. In the basic form, you take in an input of type T and a function which operates on T and returns U. Then you return the result of calling that function.

public static U Pipe<T, U>(this T input, Func<T, U> func)
{
    return func(input);
}
Enter fullscreen mode Exit fullscreen mode

NuGet / GitHub links

There are two requests to make this a first-class feature of C

If you like this concept, please support these requests to make the forward pipe operator part of the language. I have no affiliation with either author, but I would love to see an even more succinct syntax than what my package can provide.

Top comments (0)