C# 管道模式和一次性对象
最近我开始研究一种C# 管道模式和一次性对象,c#,.net,design-patterns,pipeline,C#,.net,Design Patterns,Pipeline,最近我开始研究一种管道模式,也称为管道和过滤器。我认为这是一个很好的方法来构造只处理数据的代码和应用程序。 我将此作为管道和步骤实现的基础(但这并不重要)。 像往常一样,博客涵盖了简单的场景,但就我而言,我需要(或者可能不需要)处理IDisposable对象,这些对象可能会在整个过程中传播 比如溪流 让我们考虑一个简单的流水线,它应该加载CSV文件并把它的行插入到一些DB中。在简单的抽象中,我们可以实现这样的功能 Stream Step1(string filePath) IEnumerable
管道
模式,也称为管道和过滤器
。我认为这是一个很好的方法来构造只处理数据的代码和应用程序。
我将此作为管道和步骤实现的基础(但这并不重要)。
像往常一样,博客涵盖了简单的场景,但就我而言,我需要(或者可能不需要)处理IDisposable
对象,这些对象可能会在整个过程中传播
比如溪流
让我们考虑一个简单的流水线,它应该加载CSV文件并把它的行插入到一些DB中。在简单的抽象中,我们可以实现这样的功能
Stream Step1(string filePath)
IEnumerable<RowType> Step2(Stream stream)
bool Step3(IEnumerable<RowType> data)
流步骤1(字符串文件路径)
IEnumerable步骤2(流)
布尔步骤3(IEnumerable数据)
现在我的问题是,这是否是一个好办法。因为如果我们一步一步地实现,那么流
对象离开第一步,很容易陷入内存泄漏问题。
我知道有些人可能会说我应该有Step1
,这是加载和反序列化数据,但我们正在考虑简单的过程。我们可能会遇到更复杂的情况,其中通过一条流更有意义
我想知道如何实现这样的管道,以避免内存泄漏,并避免将整个文件加载到
MemoryStream
(这样更安全)。我是否应该以某种方式将每个步骤包装在try..catch
块中,以便在出现问题时调用Dispose()
?或者我应该将所有IDisposable
资源传递到Pipeline
对象中,该对象将使用包装,以正确处理处理过程中生成的所有资源吗?如果计划像步骤3(步骤2(步骤1(文件路径)))那样使用,那么
Step2
应处理流。它可以使用c#的yield return
功能,该功能创建一个under的实现,该实现实现了IDisposable
,并允许“订阅”完成枚举和调用流的“事件”。此时,Dispose
。例如:
IEnumerable<RowType> Step2(Stream stream)
{
using(stream)
using(StreamReader sr = new StreamReader(stream))
{
while(!sr.EndOfStream)
{
yield return Parse(sr.ReadLine()); //yield return implements IEnumerator<>
}
} // finally part of the using will be called from IEnumerator<>.Dispose()
}
或foreach
bool Step3(IEnumerable<RowType> data)
{
foreach(var item in data)
if(SomeDecisionLogic(item)))
return true;
}
bool步骤3(IEnumerable数据)
{
foreach(数据中的var项)
if(某些决策逻辑(项)))
返回true;
}
对于枚举,它们都保证调用IEnumerator.Dispose()
(,),这将调用Stream.Dispose
如果交互在至少两个不同的系统之间,并且工作可以并行执行,那么拥有一个管道是值得的。否则会增加开销
在这种情况下,有两个系统:CSV文件所在的文件系统和数据库。我认为管道应该至少有两个并行运行的步骤:
IEnumerable<Row> ReadFromCsv(string csvFilePath)
void UpdateDabase<IEnumerable<Row> rows)
我猜范围取决于步骤,而步骤又取决于您根据需要设计管道的方式。可能需要重新考虑该模式在这种情况下是否有用。您可以编写此命令将数据反序列化到集合中,然后将项目插入数据库。那是两门课。你一个接一个地调用它们。即使我使用某种管道,我仍然会将它们隔离,并从管道步骤调用它们。因此,这就提出了一个问题——在这种情况下,将它们安排在管道中增加了什么?看起来我们可以在管道中安排任何顺序步骤,但我们应该吗?@ScottHannen基本上如果我使用这种模式,那么与团队其他成员的沟通会更容易——每个成员都有不同的经验。因此,准备很少的接口并告诉团队“我们应该使用管道模式”是一个很大的优势。这样,设计步骤和在开发人员之间共享工作就很容易了。我完全同意你的看法。若我想在项目中完成这项工作,那个么我不介意。虽然界面共享工作非常棒,但你们不需要管道来完成这项工作。这甚至可能让意图变得不那么清晰。可以将两个抽象定义为接口:一个反序列化,另一个写入数据库。应用程序依赖于这两种抽象。然后不同的开发人员可以在他们的实现上工作。我还没用过,所以我的答案和这个相比可能完全错了。谢谢。正如我刚才所说。我觉得对这些过程使用模式有助于团队更好地沟通和分享工作。您的观点非常有用谢谢,看起来在步骤之外传递流不是问题-我们只需要确保在任何场景中都不会忘记处理它是的,并且foreach
/LINQ
方法会在IEnumerator
上自动调用dispose
IEnumerable<Row> ReadFromCsv(string csvFilePath)
void UpdateDabase<IEnumerable<Row> rows)
IEnumerable<Row> ReadFromCsv(path)
{
using(var stream = File.OpenRead(path))
{
var lines = GetLines(stream); // yield one at a time, not all at once
foreach (var line in line) yield return GetRow(line);
}
}