InFarAday
InFarAday
CC#
Created by InFarAday on 5/18/2023 in #help
❔ LINQ expressions with "pipeline" pattern
Basically I need to process an AST in multiple steps to generate a LINQ expression from it. To that end, I use a pipeline system that can extended at will. Each part of the pipeline is not coupled to any other but uses contract objects to communicate. For instance:
C#
public class Part1 : IProcessor
{
// DI properties and constructor

public void Invoke()
{
// do something
this.Part1Result.Result = x;
}
}

public class Part1Result
{
public Something Result {get;}

public implicit operator Something(Part1Result wrapper) => wrapper.Result;
//...
}
C#
public class Part1 : IProcessor
{
// DI properties and constructor

public void Invoke()
{
// do something
this.Part1Result.Result = x;
}
}

public class Part1Result
{
public Something Result {get;}

public implicit operator Something(Part1Result wrapper) => wrapper.Result;
//...
}
And then in a later processor:
C#
public class Part2 : IProcessor
{
public Part1Result PreviousResult {get;}

public Part2(Part1Result p1result)
{
PreviousResult = p1result;
}

public void Invoke()
{
//...
}
}
C#
public class Part2 : IProcessor
{
public Part1Result PreviousResult {get;}

public Part2(Part1Result p1result)
{
PreviousResult = p1result;
}

public void Invoke()
{
//...
}
}
My main problem comes from the fact that LINQ expressions are built with the more nested elements first. In my case though I need to build the expression beginning by the end. For instance, in the expression Expression.Call(..., root, x, y, z); I will only know root at the end of the pipeline. But I can't build the actual expression since if I don't know root I can't use Expression.Call on it. What I did so far to solve this issue was add delegates in my results, like this:
C#
public void Invoke()
{
Expression something = ...;

ResultWrapper.Result = root => Expression.Call(method, root, something);
}
C#
public void Invoke()
{
Expression something = ...;

ResultWrapper.Result = root => Expression.Call(method, root, something);
}
And then the later part of the pipeline calls the delegate. This means that it will be the absolute last part of the pipeline that will end up doing all of the work. This works fine but performance concerns aside (because so many variables will be captured) I don't think it's a very sound design pattern. What do you think?
2 replies