csharplang/meetings/2017/LDM-2017-04-18.md
2017-06-01 17:24:46 -07:00

5.2 KiB

C# Language Design Notes for Apr 18, 2017

Quote of the Day: "I don't want to require MainAsync. It sounds like mayonnaise-ink!"

Agenda

  1. Default implementations for event accessors in interfaces
  2. Reabstraction in a class of default-implemented member
  3. sealed override with default implementations
  4. Use of sealed keyword for non-virtual interface members
  5. Implementing inaccessible interface members
  6. Implicitly implementing non-public interface members
  7. Not quite implementing a member
  8. asynchronous Main

Default implementations for event accessors in interfaces

Today, syntactically, either both or neither accessor can have an implementation.

Should we allow just one to be specified? Overridden?

Conclusion

If you have only one, you probably have a bug. Let's not allow it for now.

Reabstraction in a class of default-implemented member

Should an abstract class be allowed to implicitly implement an interface member with an abstract member, even when the interface member has a default implementation?

Conclusion

Yes, of course. Adding a body to an interface member declaration shouldn't ever break an implementing class.

sealed override for default implementations

Should it be allowed? would it prevent overrides in classes or only in interfaces?

It seems odd to prevent either. Also, it is weird in connection with diamond inheritance: what if one branch is sealed?

Conclusion

Let's not allowed sealed on overrides in interfaces. The only use of sealed on interface members is to make them non-virtual in their initial declaration.

Use of sealed keyword for non-virtual interface members

Some folks find it a weird use of sealed, and that they look too much like things that can be implemented in classes.

Conclusion

We think non-virtual members in interfaces are going to be useful, but will come back to the syntax. This is a mental model tripping block.

Implementing inaccessible interface members

The way the runtime works today, a class member can happily implement an interface member that isn't accessible! That's not likely to be depended on today (no language will generate that interface), but we need to decide what semantics to have here.

We could continue to only have public members in interfaces be virtual. But if we want protected, internal and private, we should probably have it so they can only be implemented by classes that can see them. But this means that interfaces can prevent other assemblies from implementing them! This may be a nice feature - it allows closed sets of implementations.

Conclusion

This is still open, but our current stake in the ground is we should allow non-public virtual interface members, but disallow overriding or implementing them in places where they are not accessible.

Implicitly implementing non-public interface members

Would we allow non-public interface members to be implemented implicitly? If so, what is required of the accessibility of the implementing method? Some options:

  • Must be public
  • Must be the exact same accessibility
  • Must be at least as accessible

Conclusion

For now, let's simply not allow it. Only public interface members can be implicitly implemented (and only by public members). We can relax as we think through it.

Not quite implementing a member

You have a member and you implement an interface. The interface adds a new member with a default implementation, that looks like your method but doesn't quite make it an implementation. Bug? Intentional? We can't provide a warning, because it would assume it was a bug.

Conclusion

Can't do anything about this.

asynchronous Main

We've decided to allow a Main method that returns Task and Task<int>. Whether it's async or not is completely optional, and an implementation detail of the method.

This feature relies on the pattern of calling GetAwaiter().GetResult() on the returned task, and on an expectation that this call blocks until the task is complete. That is the case for the framework's implementations of Task and Task<T>.

What if someone uses an alternative implementation? We can't prevent that. They either know what they are doing, or they are asking for it.

What about other awaitable types? Should they be allowed? This would be easy enough to implement; the concern is whether it is reasonable to expect their GetResult() method to block? After all, the compiler has not previously relied on this in its use of GetResult() on awaitable types, so one would assume that no particular effort has been put into ensuring it in the implementation of those types.

If we don't allow other awaitables in Main directly, folks can easily work around it just by having an async Task Main that awaits it:

static MyTask MyMain() { ... }
static async Task Main() => await MyMain(); // problem solved

Conclusion

Let's still keep the async keyword optional.

Let's stick with the name Main instead of MainAsync for now.

Let's stick to Task and Task<T>, but refine the rule:

  1. Look for Main() or Main(string[] args)
  2. Does exactly one return void or int? If one, use that. If more, error.
  3. Otherwise, look for Task and Task<T>, where the return type of GetAwaiter().GetResult() is int or void.