2

I'm trying to get array of bytes from my model to put it in the file. I have a method as such:

        public static byte[] GetByteArray(List<MyModel> models)
    {
        using var ms = new MemoryStream();
        using var sw = new StreamWriter(ms);

        foreach (var model in models)
        {
            sw.Write(model.Id + "," + model.Name);
            sw.WriteLine();
        }

        sw.Dispose();
        return ms.ToArray();
    }

This method works fine, but as may think I don't need to dispose StreamWriter manually, cause I have a using statement. I thought as well, but when I remove sw.Dispose(); the ms.ToArray(); returns an empty array. Can someone explain this behavior to me?

4 Answers4

6

You have the line:

using var sw = new StreamWriter(ms);

This only disposes the StreamWriter at the end of the method. However you're calling ms.ToArray() before the end of the method. This means that you're calling ms.ToArray() before the StreamWriter is disposed.

However, the StreamWriter is buffering some data internally, and only flushes this out to the MemoryStream when it is disposed. You therefore need to make sure you dispose the StreamWriter before calling ms.ToArray().

It's probably clearer to use the older using syntax, which is explicit about when the disposal happens:

public static byte[] GetByteArray(List<MyModel> models)
{
    using var ms = new MemoryStream();
    using (var sw = new StreamWriter(ms))
    {
        foreach (var model in models)
        {
            sw.Write(model.Id + "," + model.Name);
            sw.WriteLine();
        }
    }

    return ms.ToArray();
}
canton7
  • 37,633
  • 3
  • 64
  • 77
  • The StreamWriter takes ownership (yep, I don't like that construct either). The memorystream doesn't need to be disposed. – Jeroen van Langen Dec 03 '21 at 14:34
  • Yes, and strictly speaking it's being accessed after being disposed as well. However `MemoryStream.ToArray()` is safe to this, and all objects are safe to being disposed multiple times – canton7 Dec 03 '21 at 14:37
  • I don't like the idea of disposing multiple times. – Jeroen van Langen Dec 03 '21 at 15:12
  • @JeroenvanLangen Classes must be written to be safe to being disposed multiple times, and in cases like this it results in code which is IMO clearer and more obviously correct, since you don't need to remember whether `StreamWriter` takes ownership of the `Stream` in order to clearly see that the code does the right thing – canton7 Dec 03 '21 at 15:22
  • I agree on that, it's part of the disposing pattern, Perhaps I am (overly) precise in structure. The thing is (for example). If you open a FileStream and you create a StreamWriter on it. When the streamwriter is disposed, you are not able to use the filestream anymore. (but thats something else, how streamwriter is implemented. I probably would use the `leaveOpen` overload always ;-) – Jeroen van Langen Dec 03 '21 at 16:19
4

The dispose does part of the job. It flushes the writer. Use Flush() to flush it manually.

public static byte[] GetByteArray(List<MyModel> models)
{
    var ms = new MemoryStream();
    using var sw = new StreamWriter(ms);

    foreach (var model in models)
    {
        sw.Write(model.Id + "," + model.Name);
        sw.WriteLine();
    }

    // flush the writer, to make sure it is written to the stream.
    sw.Flush();
    return ms.ToArray();
}

You don't need to dispose the memory stream, because the StreamWriter takes ownership.

I don't like the construct that the streamwriter takes ownage of the memory stream. This is probably because there the streamwriter can also be used directly on a file. A constructor which has a file path as parameter. (so no stream parameter is needed)

StreamWriter leaveOpen constructor

Jeroen van Langen
  • 21,446
  • 3
  • 42
  • 57
  • Or set `new StreamWriter(ms) { AutoFlush = true };` – Auditive Dec 03 '21 at 13:54
  • 1
    There's little point in having both `using` and `sw.Dispose()` here – canton7 Dec 03 '21 at 13:59
  • 1
    I don't think this answers the question. I think the OP is asking why they needed the explicit `sw.Dispose()` when they already have a `using` statement. Adding an additional redundent `sw.Flush()` call doesn't explain this (and it's unnecessary, as the `sw.Dispose()` is already flushing). – canton7 Dec 03 '21 at 14:09
  • True, it was a copy/paste mistake. The dispose of the memorystream can be removed. – Jeroen van Langen Dec 03 '21 at 14:33
1

If you writing List<MyModel> items as strings, you can simplify conversion by:

public static byte[] GetByteArray(List<MyModel> models) =>
    Encoding.UTF8.GetBytes(string.Join(Environment.NewLine, 
                                       models.Select(model => $"{model.Id},{model.Name}")));

Or use third-party serializers, such from Newtonsoft.Json (example from here):

public static byte[] Serialize<T>(this T source)
{
    var asString = JsonConvert.SerializeObject(source, SerializerSettings);
    return Encoding.Unicode.GetBytes(asString);
}

public static T Deserialize<T>(this byte[] source)
{
    var asString = Encoding.Unicode.GetString(source);
    return JsonConvert.DeserializeObject<T>(asString);
}
Auditive
  • 1,607
  • 1
  • 7
  • 13
  • Yes, but Newtonsoft.Json would need more tweaks to get the same result. By default it would serialize all properties, not just id and name. – Charles Dec 03 '21 at 14:09
  • @Charles, I can serialize only some properties and deserialize them back to MyModel. Yep, i lose other properties (which I not include on serializing), but it is possible: https://dotnetfiddle.net/A8NJep – Auditive Dec 03 '21 at 14:47
1

As the others have mentioned you have to Flush the StreamWriter
This is what your function looks like:

public static byte[] GetByteArray(List<MyModel> models)
{
    MemoryStream memoryStream = new MemoryStream();
    try
    {
        StreamWriter streamWriter = new StreamWriter(memoryStream);
        try
        {
            List<MyModel>.Enumerator enumerator = models.GetEnumerator();
            try
            {
                while (enumerator.MoveNext())
                {
                    MyModel current = enumerator.Current;
                    streamWriter.Write(string.Concat(current.Id, ",", current.Name));
                    streamWriter.WriteLine();
                }
            }
            finally
            {
                ((IDisposable)enumerator).Dispose();
            }
            streamWriter.Dispose();
            return memoryStream.ToArray();
        }
        finally
        {
            if (streamWriter != null)
            {
                ((IDisposable)streamWriter).Dispose();
            }
        }
    }
    finally
    {
        if (memoryStream != null)
        {
            ((IDisposable)memoryStream).Dispose();
        }
    }
}
Charles
  • 2,721
  • 1
  • 9
  • 15