
# C# LDM for April 29, 2019

## Agenda

1. Default interface implementations and `base()` calls
2. Async iterator cancellation
3. Attributes on local functions

## Discussion

### Default interface implementations and `base()` calls

The way `base.` works in classes, if the base implementation that was present at compile
time is removed at rune time, the CLR will search for the next implementation in the
hierarchy and use that instead. For example,

```C#
class A
{
    public virtual void M() { }
}
class B : A
{
    public override void M() { }
}
class C : B
{
    public override void M() { base.M(); }
}
```

if `B.M` is not present at run time, `A.M()` will be called. For `base()` and interfaces, this is
not supported by the runtime, so the call will throw an exception instead. We'd like to add
support for this in the runtime, but it is too expensive to make this release.

We have some workarounds, but they do not have the behavior we want, and are not the
preferred codegen. Our implementation for C# is somewhat workable, although not exactly what we
would like, but the VB implementation would be much more difficult. Moreover, the implementation
for VB would require the interface implementation methods to be public API surface.

#### Conclusion

Cut `base()` syntax for C# 8. We intend to bring this back in the next major release.

### Async iterator cancellation

We’ve discussed but not settled on a behavior for when you have:

```C#
IAsyncEnumerable<T> enumerable = SomeIteratorAsync(ct1);
IAsyncEnumerator<T> enumerator = enumerable.GetAsyncEnumerator(ct2);
```

In some cases, the answer is easy:

 * If ct1 == ct2, it doesn’t matter, use ct1.

 * If ct1 == default, use ct2.

 * If ct2 == default, use ct1.

But it leaves the hard case, where `ct1 != default && ct2 != default && ct1 != ct2`. There are several options for how to handle that:

1. Use ct1, ignore ct2.
1. Use ct2, ignore ct1.
1. Throw from GetAsyncEnumerator (the point at which we can see both).
1. Use a new ct3 that combines ct1 + c2 (it’ll be canceled when either is canceled).

The current bits do (2).

There was previous support for (3).

But in our most recent discussion, (4) seems useful.

Pros for (4):

* It’s intuitive and explainable. No matter what token you pass where, the implementation will respect it. Canceling a token will request cancellation of the enumeration, regardless of where you pass it.

* It composes naturally. The code creating the iterator can pass a token. The code enumerating the iterator can pass a token. Both are respected.

* Code reviews / debugging will be easier. In our experience diagnosing production hangs, in multiple cases it was because some code in the Azure SDK didn’t pass along a CancellationToken it was supposed to. Once we knew where to look, it was very obvious just from looking at the source that the CancellationToken passed into method A wasn’t passed to method B. If we do (1) or (2), it’ll be very difficult to spot such issues, as the developer will correctly be passing along the token, but the compiler-generated implementation will ignore it, in code not visible anywhere.

Cons for (4):

* Compiler-generated code. There’s more of it, with an additional BCL dependency (CancellationTokenSource).

* Slightly larger object size. The state machine will need a CancellationTokenSource field that represents the combined source. The field (but not the instance) will be necessary even in the common case where there’s only one token and combining isn’t necessary.

* More work in GetAsyncEnumerator. In the case where either or both tokens are default, it would just add one or two comparisons. In the case where both are non-default and need to be combined, it’d involve some allocation and additional work, but pay-for-play.

#### Conclusion

Agreed on (4). Produce an error if you have the EnumeratorCancellationAttribute on more than one
CancellationToken. Warn if you have a CancellationToken parameter with no attribute.


### Triage

Remainder of time left over for triaging C# 8.0 features.

New tentative scheduling: https://github.com/dotnet/csharplang/projects/4


### Attributes on local function parameters

We realized that attributes are not permitted on local function parameters,
which means that you cannot use the `EnumeratorCancellation` attribute in
local function iterators.

**Conclusion**

Allow attributes on local function parameters and type parameters.