C# 如何将TPL数据流TranformBlock或ActionBlock放在单独的文件中?

C# 如何将TPL数据流TranformBlock或ActionBlock放在单独的文件中?,c#,.net-core,tpl-dataflow,C#,.net Core,Tpl Dataflow,我想为我的.NET核心应用程序使用TPL数据流,然后 我希望将每个TransformBlock和ActionBlock(我还不需要其他的)分离到它们自己的文件中,而不是将所有逻辑放在一个文件中。将整数转换为字符串的小TransformBlock示例 class IntToStringTransformer : TransformBlock<int, string> { public IntToStringTransformer() : base(number => nu

我想为我的.NET核心应用程序使用TPL数据流,然后

我希望将每个
TransformBlock
ActionBlock
(我还不需要其他的)分离到它们自己的文件中,而不是将所有逻辑放在一个文件中。将整数转换为字符串的小
TransformBlock
示例

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

不幸的是,这不起作用,因为块类是密封的。有没有一种方法可以将这些块组织到它们自己的文件中?

正如@Panagiotis所解释的,我认为您必须稍微抛开面向对象的思维方式。 数据流所拥有的是构建块,您可以配置这些构建块来执行所需的操作。我将尝试创建一个小示例来说明我的意思:

// 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
     }
}
//接口和impl。在不同的文件中。事实上,他们可以
//即使是在一个不同的项目。。。
公共接口IMY复杂转换
{
任务转换功能(int输入);
}
公共类MyComplexedTransform:IMYComplexedTransform
{
公共任务IMYComplexedTransform.TransformFunction(int输入)
{
//一些复杂的逻辑
}
}
类DataFlowUsingClass{
私有只读IMY复杂转换myTransformer;
私有只读转换块myTransform;
//……还有几个街区。。。
公共数据流usingclass()
{
myTransformer=new myComplementTransform();//是否可以使用ctor注入?
CreatePipeline();
}
私有void CreatePipeline()
{
//创建块
myTransform=新的TransformBlock(myTransformer.TransformFunction);
//…再过几个街区
//TODO链接块
}
}
我认为这是最接近你想要做的

最终得到的是一组可以独立测试的接口和实现。客户机基本上归结为“gluecode”


编辑:@Panagiotis正确地指出,接口甚至是多余的。你可以不用。

正如@Panagiotis所解释的,我认为你必须稍微抛开面向对象的思维方式。 数据流所拥有的是构建块,您可以配置这些构建块来执行所需的操作。我将尝试创建一个小示例来说明我的意思:

// 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
     }
}
//接口和impl。在不同的文件中。事实上,他们可以
//即使是在一个不同的项目。。。
公共接口IMY复杂转换
{
任务转换功能(int输入);
}
公共类MyComplexedTransform:IMYComplexedTransform
{
公共任务IMYComplexedTransform.TransformFunction(int输入)
{
//一些复杂的逻辑
}
}
类DataFlowUsingClass{
私有只读IMY复杂转换myTransformer;
私有只读转换块myTransform;
//……还有几个街区。。。
公共数据流usingclass()
{
myTransformer=new myComplementTransform();//是否可以使用ctor注入?
CreatePipeline();
}
私有void CreatePipeline()
{
//创建块
myTransform=新的TransformBlock(myTransformer.TransformFunction);
//…再过几个街区
//TODO链接块
}
}
我认为这是最接近你想要做的

最终得到的是一组可以独立测试的接口和实现。客户机基本上归结为“gluecode”


编辑:@Panagiotis正确地指出,接口甚至是多余的。数据流步骤/块/goroutines本质上是功能性的,最好作为工厂函数的模块组织,而不是单独的类。TPL数据流管道与F#或任何其他语言中的函数调用管道非常相似。事实上,人们可以将其视为PowerShell管道,但它更易于编写

无需创建类或实现接口来向管道添加新函数,只需添加它并将输出重定向到下一个函数即可

TPL数据流块已经提供了构造管道的原语,并且只需要一个转换函数。这就是为什么它们是密封的,以防止误用

组织数据流的自然方式也类似于F#-创建具有执行每个任务的函数的库,将它们放在相关函数的模块中。这些函数是无状态的,因此它们可以很容易地进入静态库,就像扩展方法一样

例如,可能有一个模块用于执行批量插入或读取数据的数据库相关函数,另一个模块用于处理各种文件格式的导出,单独的类用于调用外部web服务,另一个模块用于解析特定的消息格式

一个真实的例子

在过去的7年里,我一直在为一家在线旅行社(OTA)处理几个复杂的管道。其中一个呼叫几个GDS(在线旅行社和航空公司之间的中介机构)来检索交易信息——机票发行、退款、取消等。下一步检索机票记录、详细的机票信息。最后,将记录插入数据库

GDS太大了,无法使用标准,所以他们的“SOAP”web服务甚至都不符合SOAP,更不用说遵循WS-*标准了。因此,每个GDS都需要一个单独的类库来调用服务和解析输出。那里还没有数据流,项目已经够复杂了

将数据写入数据库几乎总是一样的,因此有一个单独的项目,其方法采用
IEnumerable
并使用
SqlBulkCopy
将数据写入数据库

不过,仅仅加载新数据是不够的,事情经常出错,所以我需要能够加载已经存储的票证信息

组织机构

为了保持理智:

  • 每个管道都有自己的文件:
    • 每日风笛