csharplang/meetings/2019/LDM-2019-04-29.md
2020-01-24 18:22:11 -05:00

4.4 KiB
Raw Blame History

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,

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

Weve discussed but not settled on a behavior for when you have:

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

In some cases, the answer is easy:

  • If ct1 == ct2, it doesnt 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.
  2. Use ct2, ignore ct1.
  3. Throw from GetAsyncEnumerator (the point at which we can see both).
  4. Use a new ct3 that combines ct1 + c2 (itll 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):

  • Its 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 didnt 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 wasnt passed to method B. If we do (1) or (2), itll 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. Theres 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 theres only one token and combining isnt 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, itd 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.