4.4 KiB
C# LDM for April 29, 2019
Agenda
- Default interface implementations and
base()
calls - Async iterator cancellation
- 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
heirarchy 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
We’ve 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 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:
- Use ct1, ignore ct2.
- Use ct2, ignore ct1.
- Throw from GetAsyncEnumerator (the point at which we can see both).
- 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.