Skip to content

Added examples for Memory<T> guidance #343

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Oct 4, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions snippets/standard/buffers/memory-t/owner-using/owner-using.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
using System;
using System.Buffers;

class Example
{
static void Main()
{
using (IMemoryOwner<char> owner = MemoryPool<char>.Shared.Rent())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What are the guarantees about the size of the returned buffer when you don't specify minBufferSize, like here? The documentation for MemoryPool<T> is missing, so that doesn't help.

WriteInt32ToBuffer relies on the fact that it's at least ~11 bytes, which is almost certainly nowhere near the limit. But most Memory-related code will have to deal with the possibility of a too small buffer somehow, so maybe it's worth handling here?

{
Console.Write("Enter a number: ");
try {
var value = Int32.Parse(Console.ReadLine());

var memory = owner.Memory;
WriteInt32ToBuffer(value, memory);
DisplayBufferToConsole(memory.Slice(0, value.ToString().Length));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you use value.ToString(), then that defeats the purpose of using Memory, because it allocates. I think a better option would be if WriteInt32ToBuffer returned how many bytes it wrote.

}
catch (FormatException) {
Console.WriteLine("You did not enter a valid number.");
}
catch (OverflowException) {
Console.WriteLine($"You entered a number less than {Int32.MinValue:N0} or greater than {Int32.MaxValue:N0}.");
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How are these catches useful here? I would understand them if they were related to Memory, but they're not.

}
}

static void WriteInt32ToBuffer(int value, Memory<char> buffer)
{
var strValue = value.ToString();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this use int.TryFormat to avoid the string allocation?


var span = buffer.Slice(0, strValue.Length).Span;
strValue.AsSpan().CopyTo(span);
}

static void DisplayBufferToConsole(Memory<char> buffer) =>
Console.WriteLine($"Contents of the buffer: '{buffer}'");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similarly, I think it would be better if this code didn't allocate, but I can't think of a reasonable way to achieve that here.

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.1</TargetFramework>
<RootNamespace>Visual_Studio_Projects</RootNamespace>
</PropertyGroup>

</Project>
43 changes: 43 additions & 0 deletions snippets/standard/buffers/memory-t/owner/owner.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
using System;
using System.Buffers;

class Example
{
static void Main()
{
IMemoryOwner<char> owner = MemoryPool<char>.Shared.Rent();

Console.Write("Enter a number: ");
try {
var value = Int32.Parse(Console.ReadLine());

var memory = owner.Memory;

WriteInt32ToBuffer(value, memory);

DisplayBufferToConsole(owner.Memory.Slice(0, value.ToString().Length));
}
catch (FormatException) {
Console.WriteLine("You did not enter a valid number.");
}
catch (OverflowException) {
Console.WriteLine($"You entered a number less than {Int32.MinValue:N0} or greater than {Int32.MaxValue:N0}.");
}
finally {
owner?.Dispose();
}
}

static void WriteInt32ToBuffer(int value, Memory<char> buffer)
{
var strValue = value.ToString();

var span = buffer.Span;
for (int ctr = 0; ctr < strValue.Length; ctr++)
span[ctr] = strValue[ctr];
}

static void DisplayBufferToConsole(Memory<char> buffer) =>
Console.WriteLine($"Contents of the buffer: '{buffer}'");

}
9 changes: 9 additions & 0 deletions snippets/standard/buffers/memory-t/owner/owner.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.1</TargetFramework>
<RootNamespace>Visual_Studio_Projects</RootNamespace>
</PropertyGroup>

</Project>
24 changes: 24 additions & 0 deletions snippets/standard/buffers/memory-t/ownerless/ownerless.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using System;

class Example
{
static void Main()
{
Memory<char> memory = new char[64];

Console.Write("Enter a number: ");
var value = Int32.Parse(Console.ReadLine());

WriteInt32ToBuffer(value, memory);
DisplayBufferToConsole(memory);
}

static void WriteInt32ToBuffer(int value, Memory<char> buffer)
{
var strValue = value.ToString();
strValue.AsSpan().CopyTo(buffer.Slice(0, strValue.Length).Span);
}

static void DisplayBufferToConsole(Memory<char> buffer) =>
Console.WriteLine($"Contents of the buffer: '{buffer}'");
}
9 changes: 9 additions & 0 deletions snippets/standard/buffers/memory-t/ownerless/ownerless.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.1</TargetFramework>
<RootNamespace>Visual_Studio_Projects</RootNamespace>
</PropertyGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// <Snippet1>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why does this file have two separate <Snippet1> tags?

using System;
using System.Buffers;
using System.IO;
using System.Threading.Tasks;

public class Example
{
// <Snippet1>
// An acceptable implementation.
static void Log(ReadOnlyMemory<char> message)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd prefer seeing this method return the Task so that it can be awaited.

Unless, what you're showing is how ReadOnlyMemory is limited in it use because it is a ref struct. I didn't compile this code, but I think this use is allowed, but the caller may be limited.

{
// Run in the background so that we don't block the main thread while performing IO.
Task.Run(() => {
StreamWriter sw = File.AppendText(@".\input-numbers.dat");
sw.WriteLine(message);
sw.Flush();
});
}
// </Snippet1>

// user code
public static void Main()
{
using (var owner = MemoryPool<char>.Shared.Rent())
{
var memory = owner.Memory;
var span = memory.Span;
while (true)
{
int value = Int32.Parse(Console.ReadLine());
if (value < 0)
return;

int numCharsWritten = ToBuffer(value, span);
Log(memory.Slice(0, numCharsWritten));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If Log returns a Task, this can be awaited (and main can be an async main method).

}
}
}

private static int ToBuffer(int value, Span<char> span)
{
string strValue = value.ToString();
int length = strValue.Length;
strValue.AsSpan().CopyTo(span.Slice(0, length));
return length;
}
}
// </Snippet1>

// Possible implementation of Log:
// private static void Log(ReadOnlyMemory<char> message)
// {
// Console.WriteLine(message);
// }
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.1</TargetFramework>
<RootNamespace>void_returning</RootNamespace>
</PropertyGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
using System;
using System.Buffers;
using System.IO;
using System.Threading.Tasks;

public class Example
{
// <Snippet1>
// An acceptable implementation.
static Task Log(ReadOnlyMemory<char> message)
{
// Run in the background so that we don't block the main thread while performing IO.
return Task.Run(() => {
StreamWriter sw = File.AppendText(@".\input-numbers.dat");
sw.WriteLine(message);
sw.Flush();
});
}
// </Snippet1>

// user code
public static void Main()
{
using (var owner = MemoryPool<char>.Shared.Rent())
{
var memory = owner.Memory;
var span = memory.Span;
while (true)
{
int value = Int32.Parse(Console.ReadLine());
if (value < 0)
return;

int numCharsWritten = ToBuffer(value, span);
Log(memory.Slice(0, numCharsWritten));
}
}
}

private static int ToBuffer(int value, Span<char> span)
{
string strValue = value.ToString();
int length = strValue.Length;
strValue.AsSpan().CopyTo(span.Slice(0, length));
return length;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.1</TargetFramework>
<RootNamespace>void_returning</RootNamespace>
<LangVersion>latest</LangVersion>
</PropertyGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// <Snippet1>
using System;
using System.Buffers;
using System.IO;
using System.Threading.Tasks;

public class Example
{
// <Snippet1>
// An acceptable implementation.
static void Log(ReadOnlyMemory<char> message)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comment on async as the previous samples.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That won't work, since spans aren't allowed in async methods.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@rpetrusha @BillWagner does the code use this ReadOnlyMemory struct? Then it's not ref struct, but usual readonly struct. Then, I expect it can be used in async methods. Or I'm wrong here?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@pkulikov You are right. I read Ron's related PR in dotnet/docs and yes, Memory and ReadonlyMemory are not ref structs.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, I see: though the Log might be async, it, anyway, cannot be awaited in the Main as Main contains spans and cannot be async. And not awaiting async methods might be not a good example.

{
// Run in the background so that we don't block the main thread while performing IO.
Task.Run(() => {
string defensiveCopy = message.ToString();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this defensive copy is too late. The Task could start executing long after Log returns, at which point message could be already in a different state or destroyed.

The defensive copy should happen before the Task.Run and the lambda should only access defensiveCopy, not message. (At which point I think it would be possible to even use Span instead of Memory.)

StreamWriter sw = File.AppendText(@".\input-numbers.dat");
sw.WriteLine(defensiveCopy);
sw.Flush();
});
}
// </Snippet1>

// user code
public static void Main()
{
using (var owner = MemoryPool<char>.Shared.Rent())
{
var memory = owner.Memory;
var span = memory.Span;
while (true)
{
int value = Int32.Parse(Console.ReadLine());
if (value < 0)
return;

int numCharsWritten = ToBuffer(value, span);
Log(memory.Slice(0, numCharsWritten));
}
}
}

private static int ToBuffer(int value, Span<char> span)
{
string strValue = value.ToString();
int length = strValue.Length;
strValue.AsSpan().CopyTo(span.Slice(0, length));
return length;
}
}
// </Snippet1>

// Possible implementation of Log:
// private static void Log(ReadOnlyMemory<char> message)
// {
// Console.WriteLine(message);
// }
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.1</TargetFramework>
<RootNamespace>void_returning</RootNamespace>
</PropertyGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// <Snippet1>
using System;
using System.Buffers;

public class Example
{
// implementation provided by third party
static extern void Log(ReadOnlyMemory<char> message);

// user code
public static void Main()
{
using (var owner = MemoryPool<char>.Shared.Rent())
{
var memory = owner.Memory;
var span = memory.Span;
while (true)
{
int value = Int32.Parse(Console.ReadLine());
if (value < 0)
return;

int numCharsWritten = ToBuffer(value, span);
Log(memory.Slice(0, numCharsWritten));
}
}
}

private static int ToBuffer(int value, Span<char> span)
{
string strValue = value.ToString();
int length = strValue.Length;
strValue.AsSpan().CopyTo(span.Slice(0, length));
return length;
}
}
// </Snippet1>

// Possible implementation of Log:
// private static void Log(ReadOnlyMemory<char> message)
// {
// Console.WriteLine(message);
// }
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.1</TargetFramework>
<RootNamespace>void_returning</RootNamespace>
</PropertyGroup>

</Project>