从函数返回内存流

27

在你们的意见中,从函数中返回一个新分配的内存流是否比将其传递到函数中更好?例如:

void Foo(MemoryStream m) 
{ 
   m.Write(somebuffer, 0, somebuffer.Length); 
}
或者
void MemoryStream Foo()
{
    MemoryStream retval = new MemoryStream();
    retval.Write(somebuffer, 0, somebuffer.Length);
    return retval;
}

3
这里有很多好的答案,但没有一个回答了为什么VS 2010会针对第二种形式给出CA2000可靠性警告。你不能处理打算返回的对象,把它返回后再处理似乎很别扭。这让我觉得第一种形式更可取。 - Peter T. LaComb Jr.
10个回答

21
这有点像询问您是否应该从方法返回字符串,还是使用StringBuilder并附加到其中。答案取决于用例。
调用方是否可能希望使用包含某些数据的现有流调用您的方法?他们是否想要使用相同的流多次调用它?如果是这样,使用MemoryStream版本将更有效率。另一方面,如果他们只需要一次性数据,则将其作为MemoryStream(或更简单地作为字节数组)返回可能更合适。
不幸的是,从描述中我们无法真正知道发生了什么。当然,您可以将两者都实现为重载,并从一个中调用另一个。

8
将内存流传递到函数中并从函数返回内存流,不应该互换使用。您描述的这两种方法具有不同的目的。
  • 将参数传递给函数是为了让函数对其进行处理。

  • 从函数返回某些内容是要求调用者对结果进行处理。

您在谈论两件不同的事情,就像苹果和橙子一样。


5

我会始终将流传递到函数中。这样可以使它与调用者选择的任何流一起工作,例如直接写入文件而不进行任何缓冲。


那么为什么会有这么多返回流的方法呢?;-) 通常被命名为Open...或GetStream...我认为这取决于流的目的。 - Pop Catalin
1
因为这是打开/创建一个流。不清楚你的方法是创建一个流还是将数据推入流中。这两件事有不同的答案。 - Marc Gravell
返回一个流表示不期望其中包含任何内容。 - Yola

2

您可以安全地从函数中返回它。由于该对象实现了IDisposable接口,因此您必须调用Dispose()或将其放在using子句中。


4
在 MemoryStream 上调用 Dispose 方法不会有任何作用,因为它并不会获取任何非托管资源。 - Mehrdad Afshari
1
@Merhdad & Jon - 并不总是正确的。如果您在流上使用了任何异步方法,它将缓存一个WaitHandle,然后Dispose方法确实会执行某些操作。因此,除非您知道没有调用过任何异步方法,否则最好将其处理掉。 - Greg Beech
请注意 - 除非您真的、真的、真的很在意这是MemoryStream,否则您应该返回一个Stream对象。 - plinth
@Mehrdad和Jon,另一方面,养成释放流的习惯并没有坏处(因为据我所知,大多数标准流都需要释放)。 - AwesomeTown
通常情况下,您需要在从静态函数接收的对象上调用dispose方法。对于从其他对象接收的IDisposable对象,这取决于API的具体实现,API必须指定应该如何处理接收到的流。 - Pop Catalin
显示剩余6条评论

1

你的第二个方案更好。如果可能的话,我总是尽量避免在函数内部修改对象。


流应该被改变,这就是它们被称为流而不是不可变数组的原因。而且,在我看来,将流传递给Save、Export或WriteXXX方法是非常常见的... - Pop Catalin
有很多对象是“应该”被改变的。然而,如果您要在函数中完全初始化对象,为什么不让函数返回对象的引用,而不是提供引用呢? - Andrew Hare
@Andrew:因为您可能想要追加到已经存在的数据中。请注意,示例代码不会重新初始化现有流 - 它只是向其中写入更多数据。 - Jon Skeet
啊 - 我明白你的意思了。问题似乎在暗示调用者会实例化MemoryStream,然后立即将其传递到函数中。我可以看到两种方法的好处,但哪一种更好取决于它们的使用方式。 :) - Andrew Hare

1

经过更深入的思考,我认为这归结于 Foo 方法的预期语义。它是:

  • 创建流的操作(例如 File.Open()
  • 修改流的操作(例如 something.WriteXml()

如果答案是“创建流”,则让它返回一个流。如果它修改流,则将流传递进去。

如果答案是“两者都有”,那么将该方法拆分为具有单一职责的方法可能是有意义的。


0

这两种方式并没有太大的区别。在第一种情况下,调用者将控制内存流,而在第二种情况下,您可以像这样做:

using (MemoryStream ms = Foo())
{
  //Do Stuff here.
}

最重要的是记得正确处理它。


1
MemoryStream是一个完全托管的对象。调用Dispose不会有任何作用。 - Mehrdad Afshari
3
并不总是如此。如果你在流上使用了任何异步方法,它将会缓存一个 WaitHandle,这时 Dispose 方法就会起作用。因此,除非你确定没有使用过任何异步方法或者永远不会使用,否则最好还是对其进行处理。 - Greg Beech

0

我会倾向于第一种方法,原因有两个:

  1. 内存流的“所有者”语义清晰。调用者创建了它,所以由调用者来处理它(这在其他持有非托管资源的流类型中更为重要)
  2. Foo可以与操作流的其他方法一起使用

话虽如此,如果Foo的主要目的是作为MemoryStream的工厂方法(类似于File.Open等),则第二种方法更合理。


0
由于流是需要显式清理的资源(内存、文件、网络),最好采用 RAII 方法来处理它们。这意味着初始化流的函数必须负责释放它们(我们在 C# 中有“using”关键字就是为此)。为了启用这种模式,我建议将流作为参数进行接受。这样,调用者可以决定何时创建和释放流。同时,让您的方法接受任何流实现;似乎它并不仅适用于 MemoryStream。

0

我更喜欢注入的形式。它消除了您的代码与MemoryStream之间的直接耦合,并使其更易于测试。

public void Foo(Stream stream)
{
    stream.Write(somebuffer, 0, somebuffer.Length);
}

现在我可以使用任何实现Stream的类(包括模拟类)来测试Foo。

通常情况下,我会在类的构造函数中进行注入,而不是在单个方法上进行注入,但基本思路是相同的。

public class FooClass
{
   public Stream FooStream { get; private set; }

   public FooClass() : this(null) { }

   public FooClass( Stream stream )
   {
       // provide a default if not specified
       this.FooStream = stream ?? new MemoryStream();
   }

   public void Foo()
   {
       this.FooStream.Write( somebuffer, 0, somebuffer.Length );
   }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接