MemoryStream in C# is a class that provides a stream implementation for in-memory data and offers several benefits over traditional file-based streams. This article will teach why, when, and how to use MemoryStream class in C#.
Let’s dive in.
What is MemoryStream?
MemoryStream
is a class in .NET that stores data in the system’s memory. It provides a stream-based mechanism and is used to handle data efficiently.
MemoryStream
implements the Stream
interface. Therefore, it implements methods and properties that allow us to read, write, and seek data in the system’s memory. It also provides a buffer that is used to store the data. Additionally, we can access the data stored in the buffer both sequentially or randomly.
MemoryStream Advantages
MemoryStream
has several advantages over other methods of storing data. For one, it is highly efficient, user-friendly, secure, flexible, and reliable. Additionally, we can use it in many applications, such as storing data in memory, processing data, storing large amounts of data, sending data over the network, and accessing data in a database.
One of the main advantages of using MemoryStream
is its high performance. Since it works directly with memory, it can also read and write data more quickly than other stream mechanisms. If you would like to learn more about Stream-related topics, please check out our article on StreamWriter and StreamReader
The second advantage of the MemoryStream
class is the simplicity of its implementation. Through our examples, we will see how simple it is to create and use MemoryStream
in our projects.
Another advantage is that MemoryStream
supports random access. That means we can access data in any order rather than just reading or writing it sequentially.
Finally, since a MemoryStream
works with in-memory data, we can use it to manipulate data without additional I/O operations.
How to Create MemoryStream in C#?
Creating and using MemoryStream
is relatively easy. MemoryStream
is a class that implements the Stream
interface, providing methods and properties that allow us to read, write, and seek data in the system’s memory.
MemoryStream
has several overloaded constructors:
public MemoryStream(); public MemoryStream(byte[] buffer); public MemoryStream(int capacity); public MemoryStream(byte[] buffer, bool writable); public MemoryStream(byte[] buffer, int index, int count); public MemoryStream(byte[] buffer, int index, int count, bool writable); public MemoryStream(byte[] buffer, int index, int count, bool writable, bool publiclyVisible);
The possible parameters are:
- buffer – an array of unsigned bytes used to create the stream
- capacity – the initial size of the internal array in bytes
- index – start position in the buffer at which the stream begins
- count – the length of the stream
- writable – determines if the stream supports writing
- publiclyVisible – if true,
GetBuffer()
, a method that returns the unsigned byte array from which the stream was created is enabled
It might be useful to note that MemoryStream
implements an IDisposable
interface. However, it does not hold any resources that should be disposed of. In practice, it is not necessary to call the Dispose()
method on it or use the class inside a using
statement. For more detail regarding this, please check the official documentation.
Now, let’s see some examples of this.
First, let’s create a class Constructors
:
public static class Constructors { public static MemoryStream SimpleConstructor() => new MemoryStream(); public static MemoryStream ByteArrayConstructor(byte[] bytes) => new MemoryStream(bytes); public static MemoryStream FullConstructor(byte[] bytes, int count) => new MemoryStream(bytes, 0, count, true, true); }
Here we define three methods we will use to create MemoryStream
objects.
Then, to make it easier for us to view MemoryStream
properties, let’s define a method ShowMemoryStreamProperties()
:
public static string ShowMemoryStreamProperties(MemoryStream memoryStream, string comment = "") { var sb = new StringBuilder(); if (!string.IsNullOrEmpty(comment)) sb.AppendLine($"{comment}\n--------------------------"); sb.AppendLine($"{"Length:",-20}{memoryStream.Length}"); sb.AppendLine($"{"Capacity:",-20}{memoryStream.Capacity}"); sb.AppendLine($"{"CanRead:",-20}{memoryStream.CanRead}"); sb.AppendLine($"{"CanSeek:",-20}{memoryStream.CanSeek}"); sb.AppendLine($"{"CanWrite:",-20}{memoryStream.CanWrite}"); sb.AppendLine($"{"CanTimeout:",-20}{memoryStream.CanTimeout}"); sb.AppendLine($"{"publiclyVisible:",-20}{memoryStream.TryGetBuffer(out _)}"); return sb.ToString(); }
This method returns a string that shows the values of MemoryStream
properties.
Let’s start with the basic constructor and analyze MemoryStream
properties:
var memoryStream = Constructors.SimpleConstructor(); var displayProperties = Methods.ShowMemoryStreamProperties(memoryStream, "Simple Constructor");
The result of the ShowMemoryStreamProperties()
method is:
Simple Constructor -------------------------- Length: 0 Capacity: 0 CanRead: True CanSeek: True CanWrite: True CanTimeout: False publiclyVisible: True
Here we can see that by default, our MemoryStream
has the Length
and Capacity
of zero. Furthermore, we can see the parameters CanRead
, CanWrite
, and CanSeek
set to true
. Finally, we can see the parameter CanTimeout
is set to false
, and the parameter publiclyVisible
is set to true
, which consequently means that the GetBuffer()
method is enabled.
Let’s see how to create a MemoryStream
from byte array:
var phrase1 = "How to Use MemoryStream in C#"; var phrase1Bytes = Encoding.UTF8.GetBytes(phrase1); memoryStream = Constructors.ByteArrayConstructor(phrase1Bytes); displayProperties = Methods.ShowMemoryStreamProperties(memoryStream, "Constructed From byte array");
Our new MemoryStream
properties are:
Constructed From byte array -------------------------- Length: 29 Capacity: 29 CanRead: True CanSeek: True CanWrite: True CanTimeout: False publiclyVisible: False
In this case, publiclyVisible
parameter is set to false
. Consequently, this means its GetBuffer()
method is disabled.
Finally, let’s see the last overloaded MemoryStream
constructor:
memoryStream = Constructors.FullConstructor(phrase1Bytes, phrase1Bytes.Length - 10); displayProperties = Methods.ShowMemoryStreamProperties(memoryStream, "Constructed Writable from byte array with GetBuffer() enabled");
And the properties display is:
Constructed Writable from byte array with GetBuffer() enabled -------------------------- Length: 19 Capacity: 19 CanRead: True CanSeek: True CanWrite: True CanTimeout: False publiclyVisible: True
The constructor attributes set the Length
, Capacity
, and publiclyVisible
parameters as expected.
Writing to MemoryStream in C#
Once we have a MemoryStream
object, we can use it to read, write and seek data in the system’s memory.
Let’s see how we can write data to the MemoryStream
object.
First, let’s define the data we want to write:
var phrase1 = "How to Use MemoryStream in C#"; var phrase1Bytes = Encoding.UTF8.GetBytes(phrase1); var phrase2 = " - explanation with examples"; var phrase2Bytes = Encoding.UTF8.GetBytes(phrase2);
We define two strings and the byte arrays created from these strings. Now let’s define our MemoryStream
object and write byte arrays to it:
memoryStream = Constructors.SimpleConstructor(); Methods.WriteToMemoryStream(memoryStream, phrase1Bytes); Methods.WriteToMemoryStream(memoryStream, phrase2Bytes); displayProperties = Methods.ShowMemoryStreamProperties(memoryStream, "Writing to MemoryStream");
Here are the properties of our MemoryStream
object:
Writing to MemoryStream -------------------------- Length: 57 Capacity: 256 CanRead: True CanSeek: True CanWrite: True CanTimeout: False publiclyVisible: True
Compared with the simple constructor example, we notice Length
and Capacity
parameters are changed after writing the data to the MemoryStream
object.
It is important to note this works only if the MemoryStream
does not have a set size. Attempt to write more data than the MemoryStream
object capacity will fail since the MemoryStream
is not extensible:
[Fact] public void WhenWritingOverTheCapacity_ThenFailure() { var memoryStream = Constructors.ByteArrayConstructor(new byte[10]); var addBytes = new byte[20]; Assert.Throws(() => memoryStream.Write(addBytes, 0, addBytes.Length)); }
Reading from MemoryStream in C#
To read data from the MemoryStream
, we define a method ReadFromMemoryStream()
:
public static List ReadFromMemoryStream(MemoryStream memoryStream) { var phrases = new List(); var buffer = new byte[10]; memoryStream.Position = 0; memoryStream.Read(buffer, 0, 10); phrases.Add(Encoding.UTF8.GetString(buffer)); buffer = new byte[20]; memoryStream.Seek(10, SeekOrigin.Begin); memoryStream.ReadAtLeast(buffer, 20); phrases.Add(Encoding.UTF8.GetString(buffer)); buffer = new byte[27]; memoryStream.Seek(-27, SeekOrigin.End); memoryStream.ReadExactly(buffer, 0, 27); phrases.Add(Encoding.UTF8.GetString(buffer)); return phrases; }
Here we read data from the MemoryStream
in parts using methods Read()
, ReadAtLeast()
, and ReadExactly()
.
Read()
reads a block of bytes from the current stream, writes it to the buffer, and advances the position within the stream. It reads up to the specified number of bytes but does not block the stream if fewer bytes are available. It takes three parameters, buffer
as a byte array, offset
which defines at which position to begin storing data, and count
, the maximum number of bytes to read.
ReadAtLeast()
is an extension method and it takes two parameters, buffer
as a byte array and minimumBytes
. It reads at least minimumBytes
from the current stream into the buffer
and advances the position in the stream by the same amount. It will block the stream until the minimum number of bytes is available.
Finally, ReadExactly()
is also an extension method that reads exactly the required number of bytes, blocks the stream until all the bytes are available, and may throw an exception if the end of the stream is reached before all the bytes are read. It takes the same parameters as the Read()
method.
Highlighted lines show different ways to set the current position within the MemoryStream
object.
For example, to seek the data in the MemoryStream
, we can set the Position
property with the Seek()
method. This method takes two parameters, an Offset
and a SeekOrigin
. The Seek()
method will seek the specified offset from the specified SeekOrigin
and return the new position.
Loading a File Using MemoryStream in C#
We often use MemoryStream
when working with project resources.
Let’s use MemoryStream
to load the image from the resource:
public static void LoadImageFromResources() { var imageMemoryStream = Constructors.ByteArrayConstructor(Resources.Image); File.WriteAllBytes("Image.jpg", imageMemoryStream.ToArray()); }
After loading the image data to MemoryStream
, we use it to save the image to the file system.
Serialization and Deserialization Using MemoryStream in C#
Another useful usage of MemoryStream
is object serialization and deserialization.
Let’s define a simple Person
class:
public class Person { public string FirstName { get; set; } public string LastName { get; set; } public int Age { get; set; } }
To serialize an object, we define a SerializeObject()
method:
public static byte[] SerializeObject(Person person) { var memoryStream = Constructors.SimpleConstructor(); using var writer = new BinaryWriter(memoryStream); writer.Write(person.FirstName); writer.Write(person.LastName); writer.Write(person.Age); return memoryStream.ToArray(); }
Here we create a MemoryStream
object and populate it with the data from the Person
instance using BinaryWriter
.
Similarly, to deserialize this data to an object, we define a method DeserializeObject()
:
public static Person DeserializeObject(byte[] serializedData) { Person deserializedPerson; var memoryStream = Constructors.ByteArrayConstructor(serializedData); using var reader = new BinaryReader(memoryStream); deserializedPerson = new Person { FirstName = reader.ReadString(), LastName = reader.ReadString(), Age = reader.ReadInt32() }; return deserializedPerson; }
Let’s see these methods in action:
var person = new Person { FirstName = "Jack", LastName = "Black", Age = 30 }; byte[] serializedData = Methods.SerializeObject(person); var deserializedPerson = Methods.DeserializeObject(serializedData);
Conclusion
In this article, we learned about MemoryStream
and analyzed its advantages over similar methods of working with the data. Furthermore, we saw how to use it to read, write and manipulate data in an easy, secure, and reliable way.
In conclusion, if you are looking for an efficient and reliable way to handle data in your applications, MemoryStream
is a great choice.