StreamWriter写入MemoryStream

27

我原本以为在 StreamWriter 对象中调用 Flush() 时会将内容写入基础流,但实际上我的代码并不是这样。

它没有向文件中写入任何内容。你有什么想法是我做错了什么吗?

    public FileResult DownloadEntries(int id)
    {
        Competition competition = dataService.GetCompetition(id);
        IQueryable<CompetitionEntry> entries = dataService.GetAllCompetitionEntries().Where(e => e.CompetitionId == competition.CompetitionId);

        MemoryStream stream = new MemoryStream();
        StreamWriter csvWriter = new StreamWriter(stream, Encoding.UTF8);

        csvWriter.WriteLine("First name,Second name,E-mail address,Preferred contact number,UserId\r\n");

        foreach (CompetitionEntry entry in entries)
        {
            csvWriter.WriteLine(String.Format("{0},{1},{2},{3},{4}",
                entry.User.FirstName,
                entry.User.LastName,
                entry.User.Email,
                entry.User.PreferredContactNumber,
                entry.User.Id));
        }

        csvWriter.Flush();

        return File(stream, "text/plain", "CompetitionEntries.csv");
    }

4
考虑使用using语句来处理MemoryStream和StreamWriter以便进行正确的垃圾回收。 - neontapir
1
@neontapir 我还没有开始整理代码,但之后会的。祝好! - ediblecode
1
@neontapir,这个特定情况下的评论不正确:这只会使问题更加严重/明显... - Alexei Levenkov
使用using语句是不必要的。一旦调用flush方法,streamWriter就会被回收。在return语句上,垃圾收集器清理其他所有内容。 - markthewizard1234
3个回答

32

我认为您需要设置Stream.Position = 0。在写入时,它会将位置推进到流的末尾。当您将其传递给File()时,它从当前位置开始 - 即末尾。

我认为以下代码可行(并未尝试编译):

stream.Position = 0;
return File(stream, "text/plain", "CompetitionEntries.csv");

这种方式不会创建任何新对象或复制基础数组。


3
请注意,在控制器代码中,您不能处置(dispose)编写器(writer)和流(stream)。复制粘贴时,您可能需要在代码中添加注释。注明上述限制。 - Alexei Levenkov

10

你的MemoryStream定位在末尾。更好的代码是使用MemoryStream(Byte[], Int32, Int32, Boolean)构造函数,在相同的缓冲区上创建新的只读内存流。

修剪后缓冲区的最简单读写操作:

 return File(new MemoryStream(stream.ToArray());

在不复制内部缓冲区的情况下进行R/o操作:

 return File(new MemoryStream(stream.GetBuffer(), 0, (int)stream.Length, false);

注意:要小心不要通过File(Stream)释放您返回的流。否则,您将会收到某种形式的"ObjectDisposedException"异常。例如,如果您仅仅将原始流的位置设置为0并将StreamWriter包装在using语句中,则会返回已释放的流。


有趣。正如我下面展示的那样,我能够使用using语句实现类似的功能。 - neontapir
1
@neontapir,你没有传递原始流,而是使用new MemoryStream(stream.ToArray())创建了一个新的未释放的流。ToArray/GetBuffer可以在已释放的MemoryStream上调用,但大多数其他方法如Read/Write则不行。 - Alexei Levenkov
当然,你是对的,@AlexeiLevenkov,我忘记了我已经进行了那个重构。 - neontapir
将 Stream.Position 设置为 0 并使用该流比创建重复的 MemoryStream 对象更高效,是吗? - David Thielen
@DavidThielen,虽然更改位置更有效率,但我更喜欢创建新的MemoryStream,因为它使代码更加明确,并允许使用正常的“using”模式来编写数据。请注意,现有缓冲区上的MemoryStream是非常小的对象,因此我认为这是正确查看代码的好代价。如果性能对于特定的代码片段如此关键,那么我不会使用MemoryStream,因为它的内存重新分配策略。 - Alexei Levenkov

3
在尝试中,我得到了以下原型的工作示例:
using System.Web.Mvc;
using NUnit.Framework;

namespace StackOverflowSandbox
{
[TestFixture]
public class FileStreamResultTest
{
    public FileStreamResult DownloadEntries(int id)
    {
        // fake data
        var entries = new[] {new CompetitionEntry { User = new Competitor { FirstName = "Joe", LastName = "Smith", Email = "jsmith@example.com", Id=id.ToString(), PreferredContactNumber = "555-1212"}}};

        using (var stream = new MemoryStream())
        {
            using (var csvWriter = new StreamWriter(stream, Encoding.UTF8))
            {
                csvWriter.WriteLine("First name,Second name,E-mail address,Preferred contact number,UserId\r\n");

                foreach (CompetitionEntry entry in entries)
                {
                    csvWriter.WriteLine(String.Format("{0},{1},{2},{3},{4}",
                                                      entry.User.FirstName,
                                                      entry.User.LastName,
                                                      entry.User.Email,
                                                      entry.User.PreferredContactNumber,
                                                      entry.User.Id));
                }

                csvWriter.Flush();
            }

            return new FileStreamResult(new MemoryStream(stream.ToArray()), "text/plain");
        }
    }

    [Test]
    public void CanRenderTest()
    {
        var fileStreamResult = DownloadEntries(1);
        string results;
        using (var stream = new StreamReader(fileStreamResult.FileStream))
        {
            results = stream.ReadToEnd();
        }
        Assert.IsNotEmpty(results);
    }
}

public class CompetitionEntry
{
    public Competitor User { get; set; }
}

public class Competitor
{
    public string FirstName;
    public string LastName;
    public string Email;
    public string PreferredContactNumber;
    public string Id;
}
}

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