# C# Language Design Meeting for April 5th, 2021

## Agenda

1. [Interpolated string improvements](#interpolated-string-improvements)
2. [Abstract statics in interfaces](#abstract-statics-in-interfaces)

## Quote of the Day

- "Every time someone tells me they have a printer that works, I believe they're part of the conspiracy"...
"So what I'm getting from this is that you've given up on your printer and are now printing at the office?"

NB: This is, to my knowledge, the largest amount of time between parts 1 and 2 of an LDM quote (approximately 1 hour and 10 minutes,
in this instance).

## Discussion

### Interpolated string improvements

https://github.com/dotnet/csharplang/issues/4487

Our focus today was in getting enough of the open questions in this proposal completed for the [dotnet/runtime API proposal](https://github.com/dotnet/runtime/issues/50601)
to move forward.

#### Create method shape

We adjusted the shape of the `Create` method from the initial proposal to facilitate some improvements:

* In some cases, `out`ing a parameter with a reference type field can cause the GC to introduce a write barrier.
* For the non-`bool` case, `Create` feels more natural.

This may be a micro-optimization, but given that this pattern is not intended to be user-written, but instead lowered to by the compiler,
we feel that it's worth it.

##### Conclusion

Approved.

#### TryFormatX method shape

The proposal suggests that we should allow `TryFormatX` calls to either return `bool` or `void`, as some builders will want to stop formatting
after a certain point if they run out of space, or if they know that their output will not be used. While a `void`-returning method named `TryX`
isn't necessarily in-keeping with C# style, we will keep the single name for a few reasons:

* Again, this pattern isn't intended to be directly consumed by the end user, they'll be writing interpolated strings that lower to this.
* It's harder for API authors to mess up and mix `void`-returning and `bool`-returning `TryFormatX` methods, because you can't overload on
return type.
* It's easier to implement.

We do want to allow mixing of a `Create` method that has an `out bool` parameter, as some builder are never going to fail after the first create
method. A logger, for example, would return `false` from `Create` if the log level wasn't enabled, but the actual format calls will always succeed.

We also looked at ensuring that a single `TryFormatInterpolationHole` method with optional parameters for both alignment and format components can
be used with this pattern. As specified today, these parameters are not named so overload resolution can only look for signatures by parameter type.
This means there's no single signature that handle just a format or just an alignment. To resolve this, we will specify the parameter names we use
for the alignment and format components (their names will be `alignment` and `format`, respectively). The first parameter will still be unnamed.

Another discussion point was on whether we should require builders to _always_ support all possible combinations of a format or alignment component
being present. For some types (such as `ReadOnlySpan<T>`), we're imagining that such components would be just ignored by the builder, and it would
be interesting to just have that be a compile error instead of just being silently ignored at runtime. However, the ship on invalid format specifiers
sailed a long time ago, and there is potential difficulty in making a good compile-time error experience for these scenarios. Thus, our recommendation
is to always include overloads that support these specifiers, even when ignored at runtime.

##### Conclusion

API shape is approved. We will use named parameters for `alignment` and `format` parameters, as appropriate.

#### Disposal

Finally in interpolated string improvements, we looked at supporting disposal of builder types. Our decision that we will have conditional execution
of interpolated string holes means that user code will be running the middle of builder code. This means that an exception can be thrown, and if the
builder acquired resources (such as renting from the `ArrayPool`), it has the potential to be lost if we do not wrap the builder (and potentially
larger method call that consumes the builder for non-`string` arguments) in a try/finally that calls dispose on the builder. There's also an additional
complication to the disposal scenario, which is whether we trust the compile-time type of the builder. If the builder is a non-sealed reference type
or a type parameter constrained to an interface type that has an `abstract static Create` method, the actual runtime type could implement `IDisposable`
that we do not know about at runtime. In the language, we're not hugely consistent on this. In some cases we trust that the compile-time type is correct
(such as when determining if a sealed/struct type is disposable), and in some cases we emit a runtime check for `IDisposable` (such as if the enumerator
type is a non-sealed type or interface). We also need to consider whether to do pattern-based `Dispose` for all types (as we do in `await foreach`) or
just for `ref struct`s (as we do for regular `foreach`).

Even the concept of disposal for these builder types might not be needed. The runtime is a bit leary of having `InterpolatedStringBuilder` be disposable,
because it means that every interpolated string will introduce a `try/finally` in a method. We really don't want to see people create performance analyzers
that tell users to avoid interpolated strings in methods because it will affect inlining decisions. We have some ideas on avoiding this for interpolated
strings converted to strings specifically, but a general pattern is concerning. If the main builder type won't be disposable, are we concerned that we're
trying to engineer a solution to a problem that doesn't actually exist?

##### Conclusion

No conclusion on this point today. A small group will examine this in more detail and make recommendations to the LDM based on evaluation.

### Abstract statics in interfaces

https://github.com/dotnet/csharplang/issues/4436

We looked at 2 major changes to the proposal today: allowing default implementations and changes to operator restrictions.

#### Default implementations

The existing proposal specifically scopes default implementations of virtual statics in interfaces out because of concerns about runtime implementation
costs. In particular, in order to make default implementations work and be able to call other virtual static members, there must be a hidden "self" type
parameter so that the runtime knows what type to call the virtual static method on. That work is still too complicated to bring into C# 10, but a simple
change of scope can make a subset of these cases work: we could require that all static virtual members _must_ be called on a type parameter. This takes
that hidden "self" parameter and makes it no longer hidden, because there must always be a thing that the user calls that actually contains the type. It's
not always necessary to write `T` itself: for example, `t1 + t2` would reference the static virtual `+` operator on `T`, so it's being accessed on the
type parameter, not on the interface.

However, there are some serious usability concerns for this approach. A default implementation of a virtual static member cannot be inherited by any
concrete types that inherit from that interface. This is true for instance methods as well, but that method (or a more derived type's implementation of
the method) can be accessed by casting that instance to the interface type in question. For a DIM of a virtual static member, there is no instance that
can be cast to the base interface to access the member, so a concrete type must always reimplement the virtual static member or it will be truly inaccessible.
For example:

```cs
interface I<T> where T : I<T>
{
    public static abstract bool operator ==(T t1, T t2);
    public static virtual bool operator !=(T t1, T t2) => !(t1 == t2);
    public void M() {}
}
class C : I<C>
{
    public static bool operator ==(C c1, C c2) => ...;
}

C c = new C();
c.M(); // M is not accessible
((I)c).M(); // This works, however

_ = c == c; // Fine: C implements ==
_ = c != c; // Not fine: I.!= is a default implementation that C does not inherit. This method cannot be called.
```

There are 2 workarounds a user could use for this problem: make a generic method that takes a type parameter constrained to I and invoking the member there,
or reimplementing the operator on C, thereby removing the benefit of the default implementation in the first place. Neither of these are particularly appealing.

##### Conclusion

A smaller group will revisit this and decide whether it is useful. We are skeptical of it based on the above problems currently.

#### Operator restrictions

Today, interfaces are prohibited from implementing `==` or `!=`, and from appearing in user-defined conversion operators. We have a long history of this, mainly
because with interfaces there is always the change that the underlying type actually implements the interface at runtime, and the language should always prefer
built-in conversions to user-defined conversions. However, these operators cannot actually be accessed when the user only has an instance of the interface, they
can only be accessed when using a concrete derived type or a type parameter constrained to the interface type. This addresses our concerns about interface
implementation at runtime, and conversions from types defined in interfaces is particularly useful for numeric contexts such as being able to allow `T t = 1;`
because `T` is constrained to an interface that has a user-defined conversion from integers.

##### Conclusion

Lifting these restrictions is approved.
