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

Conversation

rpetrusha
Copy link

@rpetrusha rpetrusha commented Oct 3, 2018

Added examples for Memory<T> guidance

Related to dotnet/docs#4823

@rpetrusha rpetrusha self-assigned this Oct 3, 2018
Copy link
Member

@BillWagner BillWagner left a comment

Choose a reason for hiding this comment

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

These are good samples @rpetrusha

I had a few suggestions, all toward the goal of showing better production-code style even though it is not related to the concepts being demonstrated here.

I'm not sure the best solution for the Task.Run call using the ReadOnlyMemory. If possible, that should be async. If not, the supporting text should explain that limitation in detail.

Console.WriteLine($"You entered a number less than {Int32.MinValue:N0} or greater than {Int32.MaxValue:N0}.");
}
finally {
owner.Dispose();
Copy link
Member

Choose a reason for hiding this comment

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

minor nit: I'd prefer owner?.Dispose(); to protect against the statement in line 8 throwing.

{
// <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.

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).

{
// <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.

@rpetrusha rpetrusha merged commit d4105f6 into dotnet:master Oct 4, 2018
@rpetrusha rpetrusha deleted the memory-t branch October 4, 2018 22:04
Copy link
Contributor

@svick svick left a comment

Choose a reason for hiding this comment

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

I apologize for commenting on a merged PR. But my comments are related to dotnet/docs#8055, which is still open and I'm not sure what would be the right fix for some of them, which is why I didn't open a PR instead.


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.

{
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?


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?

}
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 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.

@@ -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?

{
// 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.)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants