Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Example of how to use String.Create in .NET Core 2.1

Tags:

c#

.net-core

Does anyone know how this method is intended to be used? The documentation is somewhat 'light'!

public static string Create<TState> (int length, TState state, System.Buffers.SpanAction<char,TState> action);

https://docs.microsoft.com/en-us/dotnet/api/system.string.create?view=netcore-2.2

like image 247
Chris Brook Avatar asked Feb 08 '19 17:02

Chris Brook


People also ask

Is string created in the heap C#?

They are also reference types which means when you create a string, the string object is created on the heap, with a reference to its memory location on the stack.

Which is the base class for string () method in C#?

The String class is defined in the . NET base class library. In other words, a String object is a sequential collection of System.

How are strings stored in memory C#?

String just implements an indexer of type char internally and we can say that string is just equivalent to char[] type with lots of extra code to make it useful for you, hence, like an array, it is stored on heap always.


1 Answers

The String.Create() method needs three things:

  1. The final length of the string. You must know this in advance, because the method needs it to safely create an internal fixed-length buffer for the Span<char> instance used to construct the final string.
  2. The data (state) which will become your string. For example, you might have an array buffer (of, say, ascii integers received over the network), but it could be anything. This is the raw data that will be transformed into the final string. There is an example buried deep in this MSDN article that even uses a Random instance. I've also seen an incomplete example used to create a base-64 encoded hash value (fixed length) of bitmap images (variable sized state input), but sadly I can't find it again.
  3. The action lambda function that transforms state into the characters for the final string. The Create() method will call this function, passing the internal Span<char> it created for the string and your state data as the arguments.

For a very simple example, we can Create() a string from an array of characters like this:

char[] buffer = {'f', 'o', 'o'};
string result = string.Create(buffer.Length, buffer, (chars, buf) => {
    for (int i=0;i<chars.Length;i++) chars[i] = buf[i];
});

Of course, the basic string(char[]) constructor would also work here, but that shows what a correct function might look like. Or we can map an array of ascii int values to a new string like this:

int[] buffer = {102, 111, 111};
string result = string.Create(buffer.Length, buffer, (chars, buf) => {
    for (int i=0;i<chars.Length;i++) chars[i] = (char)buf[i];
});

The function exists because there are some significant potential performance wins for this technique over traditional methods. For example, rather than reading a Stream into a buffer, you could pass the Stream object directly to String.Create() (assuming you know the final length). This avoids needing to allocate a separate buffer and avoids one round of copying values (stream=>buffer=>string becomes just stream=>string).


What happens when you call string.Create() is the function allocates a new string that already has the size determined by your length argument. This is one (and only one) heap allocation. Because Create() is a member of the string type, it has access to private string data for this new object you and I normally can't see. It now uses this access to create an internal Span<char> instance pointed at the new string's internal character data.

This Span<char> lives on the stack, but acts on the heap memory from the new string... there is no additional allocation, and it's completely out of scope as soon as the Create() function returns, so everything is legal and safe. And because it's basically a pointer-with-benefits, there's virtually no risk of overflowing the stack unless you've done something else horribly wrong.

Now Create() calls your action function to do the heavy lifting of populating the string. Your action lambda can write into the Span<char>... for the duration of your lamdba's execution, strings are less-immutable than you may have heard!

When the action lamdba is finished, Create() can return the new, ready-to-use, string reference. Everything is good: we minimized heap allocations, preserved type safety and memory safety; the Span<char> is no longer accessible anywhere, and as a stack value is already destroyed. We also minimized unnecessary copying between buffers, depending on your action implementation.

like image 173
Joel Coehoorn Avatar answered Sep 18 '22 22:09

Joel Coehoorn