Search code examples
c#.net-coretpl-dataflow

How to put a TPL Dataflow TranformBlock or ActionBlock in a separate file?


I want to use the TPL Dataflow for my .NET Core application and followed the example from the docs.

Instead of having all the logic in one file I would like to separate each TransformBlock and ActionBlock (I don't need the other ones yet) into their own files. A small TransformBlock example converting integers to strings

class IntToStringTransformer : TransformBlock<int, string>
{
    public IntToStringTransformer() : base(number => number.ToString()) { }
}

and a small ActionBlock example writing strings to the console

class StringWriter : ActionBlock<string>
{
    public StringWriter() : base(Console.WriteLine) { }
}

Unfortunately this won't work because the block classes are sealed. Is there a way I can organize those blocks into their own files?


Solution

  • As @Panagiotis explained, I think you have to put aside the OOP Mindset a little. What you have with DataFlow are Buildingblocks that you configure to execute what you need. I'll try to create a little example of what I mean by that:

    // Interface and impl. are in separate files. Actually, they could 
    // even be in a different project ...
    public interface IMyComplicatedTransform
    {
         Task<string> TransformFunction(int input);
    }
    
    public class MyComplicatedTransform : IMyComplicatedTransform
    {
         public Task<string> IMyComplicatedTransform.TransformFunction(int input)
         {
             // Some complex logic
         }
    }
    
    class DataFlowUsingClass{
    
         private readonly IMyComplicatedTransform myTransformer;
         private readonly TransformBlock<int , string> myTransform;
         // ... some more blocks ...
    
         public DataFlowUsingClass()
         {
              myTransformer = new MyComplicatedTransform(); // maybe use ctor injection?
              CreatePipeline();
         }
    
         private void CreatePipeline()
         {
              // create blocks
              myTransform = new TransformBlock<int, string>(myTransformer.TransformFunction);
              // ... init some more blocks
    
              // TODO link blocks
         }
    }
    

    I think this is the closest to what you are looking for to do.

    What you end up with is a set of interfaces and implementations which can be tested independently. The client basically boils down to "gluecode".

    Edit: As @Panagiotis correctly states, the interfaces are even superfluent. You could do without.