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
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.
The String class is defined in the . NET base class library. In other words, a String object is a sequential collection of System.
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.
The String.Create()
method needs three things:
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.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.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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With