csharplang/meetings/2016/LDM-2016-08-24.md
2017-01-31 10:38:26 -08:00

3.5 KiB

C# Language Design Meeting, Aug 24, 2016

Agenda

After a meeting-free period of implementation work on C# 7.0, we had a few issues come up for resolution.

  1. What does it take to be task-like?
  2. Scope of expression variables in initializers

Task-like types

The generalization of async return types requires the return type of an async method to provide an implementation for the "builder object" that the method body's state machine interacts with when it awaits, and when it produces the returned "task-like" value.

How should it provide this implementation? There are a couple of options:

  1. A GetAsyncMethodBuilder method on the task-like type following a specific compiler-recognized pattern
  2. 1 + a modifier on the type signifying "buy-in" to being an async type
  3. An attribute that encodes the builder type

We like the attribute approach, because this is really a metadata concern. The attribute sidesteps issues with accessibility (is the type only task-like when the GetAsyncMethodBuilder method is accessible?), and doesn't pollute the member set of the type. Also it works on interfaces. For instance, it could conceivably apply to WinRT's IAsyncAction etc.

We only expect very few people to ever build their own task-like types. We may or may not choose to ever actually ship the attribute we will use. Implementers can just roll their own internal implementation. The compiler only looks for the name - which will be System.Runtime.CompilerServices.AsyncBuilderAttribute.

There are a couple of options for what the attribute should contain:

  1. Nothing - it is just a marker, and there is still a static method on the task-like type
  2. The name of a method to call on the task-like type
  3. The builder type itself
  4. A static type for getting the builder

Option 1 and 2 we've already ruled out, because we don't want to depend on accessible members on the task-like type itself.

Option 3 would require a Type argument to the constructor that is either non-generic (for void-yielding or non-generic result-yielding task-like types) or an open generic type with one type parameter (for generic result-yielding task-like types) Option 4 is essentially a "builder builder", and would let us have a less dirty relationship between type parameters on the builder and task-like:

static class ValueTaskBuilderBuilder
{
    ValueTaskBuilder<T> GetBuilder<T>(ValueTask<T> dummy);
}

That is probably a bridge too far, so we will go with Option 3.

The builder type given in the attribute is required to have a static, accessible, non-generic Create method.

This would work for IAsyncAction etc. from WinRT, assuming that a builder is implemented for them, and an attribute placed on them.

The proposal to allow the builder to be referenced from the async method is attractive, but something for another day.

Scope of expression variables in initializers

int x = M(out int t), y = t;

If this is a local declaration, t is in scope after the declaration. If this is a field declaration, though, should it be? It's problematic if it is, and inconsistent if it isn't! Also, if we decide one way or another, we can never compatibly change our mind on it.

Let's block this off and not allow expression variables to be introduced in field initializers! It's useless today, and we should save the scoping decisions for when future features give us a use case.

public C() : base(out int x)
{
	// use x?
}

A variable declared in a this or base initializer should be in scope in the whole constructor body.