2009-02-02 3 views
22

На ваш взгляд, лучше ли вернуть вновь выделенный поток памяти из функции или передать ее в функцию? Например,Возвращаемый поток памяти из функции

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; 
} 
+2

Здесь есть много хороших ответов, но ни один из них не объясняет, почему VS 2010 дает предупреждение о надежности CA2000 для второй формы. Вы не можете очень хорошо распоряжаться объектом, который собираетесь направить, и избавиться от него после его возврата, кажется неудобным. Заставляет меня думать, что первая форма предпочтительнее. –

ответ

17

Это немного похоже на вопрос, следует ли вам возвращать строку из метода или принимать StringBuilder и добавлять к нему. Ответ зависит от того, какой вариант использования.

Возможно ли, что вызывающий абонент захочет вызвать ваш метод с существующим потоком, содержащим некоторые данные? Может, они захотят назвать это несколько раз, используя тот же поток? Если это так, версия, использующая MemoryStream, будет более эффективной. С другой стороны, если они хотят получить данные только один раз, то, возможно, более подходящим будет вернуть его как MemoryStream (или, проще говоря, в виде байтового массива).

К сожалению, из описания мы не можем сказать, что происходит. Конечно, вы можете реализовать как перегрузку, так и перезвонить друг другу.

2

Вы можете безопасно вернуть его из функции. Вы должны позвонить Dispose() или поместить его в поле using, потому что оно реализует IDisposable.

+3

Вызов Dispose на MemoryStream ничего не делает. Он не получает никаких неуправляемых ресурсов. –

+1

+1 для комментария Мехрдада. –

+1

@Merhdad & Jon - Не всегда верно. Если вы использовали какой-либо из методов async в потоке, он будет кэшировать WaitHandle, а затем метод Dispose делает что-то. Таким образом, вы безопаснее распоряжаться им, если вы не знаете, что никакие методы асинхронизации не были или никогда не будут вызваны. –

1

Ваш второй вариант лучше. Я всегда стараюсь избегать мутации объектов внутри функций, если это вообще возможно.

+0

Потоки * сутулятся *, чтобы быть мутированными, поэтому они являются потоками, а не неизменяемыми массивами. А также передача потока в метод Save, or Export или WriteXXX довольно распространена, на мой взгляд, ... –

+0

+1 для комментария поп –

+0

Существует множество объектов, которые «предполагается» для мутирования. Однако, если вы собираетесь полностью инициализировать объект в функции, почему бы не вернуть функцию ссылку на объект, а не предоставлять ссылку? –

0

Существует не так много различий. В первом случае абонент будет иметь контроль над потоком памяти и во втором случае, вы можете сделать что-то вроде этого:

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

Самое главное помнить, чтобы избавиться от него правильно.

+1

MemoryStream - это исключительно управляемый объект. Вызов Dispose ничего не сделал. –

+3

@Merhdad - Не всегда верно. Если вы использовали какой-либо из методов async в потоке, он будет кэшировать WaitHandle, а затем метод Dispose делает что-то. Таким образом, вы безопаснее распоряжаться им, если вы не знаете, что никакие методы асинхронизации не были или никогда не будут вызваны. –

7

Передача поток памяти в функции и возвращение поток памяти для функции не должны быть использованы как взаимозаменяемые. Методы, которые вы описываете, служат двум различным целям.

  • Передача чего-либо функции происходит, когда вы хотите, чтобы функция что-то делала с параметром.

  • Возвращение чего-либо из функции происходит, когда вызывающий объект должен что-то делать с результатом.

Вы говорите о двух разных вещах, яблоках и апельсинах.

3

Я бы всегда передавал поток в функцию. Это позволяет работать с любым потоком выбора вызывающего абонента, например, прямо в файл без какой-либо буферизации.

+0

Тогда как получилось так много методов, возвращающих поток? ;-) Usualy с именем Open ... или GetStream ... Я думаю, что это зависит от того, какова цель потока. –

+1

Потому что это ** открытие **/** создание ** потока. Неясно, создает ли ваш метод поток, а не толкает данные в один. У двух вещей разные ответы. –

0

Я бы предпочел впрыснутую форму. Он удаляет прямую связь между вашим кодом и MemoryStream и делает его более проверяемым.

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

Теперь я могу протестировать Foo с любым классом, который реализует Stream, включая класс mock.

Как правило, я делал бы инъекцию в конструкторе класса, а не по отдельным методам, но идея в основном такая же.

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); 
    } 
} 
0

я бы тяготеть к первому по двум причинам:

  1. Семантика, кто «владеет» поток памяти очевидны. Вызывающий абонент создал его, поэтому его можно избавиться от него (это больше проблема с другими типами потоков, которые удерживаются на неуправляемых ресурсах).
  2. Foo может использоваться в сочетании с другими методами, которые работают на ваш поток

Тем не менее, если основная цель Foo как фабричный метод для MemoryStreams (по аналогии с чем-то вроде File.open и т.д.), то второй подход имеет больше смысла.

1

После некоторой мысли, я думаю, что это сводится к предполагаемой семантике метода Foo. Это:

  • Операция, которая создает поток (например, File.Open().)
  • Операция, которая изменяет поток (например something.WriteXml().)

Если ответ «создает поток», вернуть поток. Если он изменяет поток, передайте поток.

Если ответ «некоторые из обоих», может иметь смысл разделить метод так, чтобы он имел только одну ответственность.

0

Поскольку потоки являются ресурсами, требующими явного удаления (память, файл, сеть), лучше всего применять подход RAII к их обработке. Это означает, что функция, которая их инициализирует, должна быть ответственна за выпуск (у нас есть «использование» keyord в C# только для этого). Чтобы включить этот шаблон, я говорю о принятии потока в качестве параметра. Таким образом, вызывающий может решить, когда создавать и удалять поток. В то время как вы на нем, ваш метод принимает любую реализацию потока; не похоже, что он должен работать только для MemoryStream.