2019-02-28 22:57:58 +01:00
|
|
|
|
2019-03-11 08:56:43 +01:00
|
|
|
# C# Language Design Meeting for Feb. 27, 2019
|
|
|
|
|
2019-02-28 22:57:58 +01:00
|
|
|
## Agenda
|
|
|
|
|
|
|
|
1. Allow ObsoleteAttribute on property accessors
|
|
|
|
2. More Default Interface Member questions
|
|
|
|
|
|
|
|
## Discussion
|
|
|
|
|
|
|
|
### Allow ObsoleteAttribute on property accessors
|
|
|
|
|
|
|
|
ObsoleteAttribute, ConditionalAttribute, and CLSCompliantAttribute are
|
|
|
|
currently disallowed on property accessors. VB allows the first three, but
|
|
|
|
provides a warning for `ClSCompliant`.
|
|
|
|
|
|
|
|
The question is whether or not to loosen this restriction.
|
|
|
|
|
|
|
|
Allowing `Conditional` seems very dangerous because the "arguments" to the
|
|
|
|
method are not evaluated if the condition is false. Logically, this would
|
|
|
|
imply that the right-hand side in a property assignment expression is not
|
|
|
|
evaluated, but this seems very likely to lead to bugs and confusion.
|
|
|
|
|
|
|
|
We don't see a reason to disallow `Obsolete` or `Deprecated`, and don't
|
|
|
|
particularly care about `CLSCompliant`.
|
|
|
|
|
|
|
|
**Conclusion**
|
|
|
|
|
|
|
|
Allow the change for `Obsolete` and `Deprecated`. Leave everything else
|
|
|
|
as-is.
|
|
|
|
|
|
|
|
|
|
|
|
### Collision of lookup rules and decisions for `base()`
|
|
|
|
|
|
|
|
Example:
|
|
|
|
|
|
|
|
```C#
|
|
|
|
interface I1
|
|
|
|
{
|
|
|
|
void M(int) { }
|
|
|
|
}
|
|
|
|
|
|
|
|
interface I2
|
|
|
|
{
|
|
|
|
void M(short) { }
|
|
|
|
}
|
|
|
|
|
|
|
|
interface I3
|
|
|
|
{
|
|
|
|
override void I1.M(int) { }
|
|
|
|
}
|
|
|
|
|
|
|
|
interface I4 : I3
|
|
|
|
{
|
|
|
|
void M2()
|
|
|
|
{
|
|
|
|
base(I3).M(0) // What does this do?
|
|
|
|
}
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
The tricky part here is that both `M(short)` and `M(int)` are applicable to
|
|
|
|
`M(0)`, but lookup rules also say that if we find an applicable member in a
|
|
|
|
more derived interface, we ignore members from the less derived interfaces.
|
|
|
|
Combined with the rule that overrides are not found during lookup, when
|
|
|
|
looking in `I3` the first thing we find is `I2.M`, which is applicable,
|
|
|
|
meaning that `I1.M` does not appear in the list of applicable members.
|
|
|
|
|
|
|
|
Since we concluded in the previous meeting that an implementation
|
|
|
|
*must* exist in the target type, and `I2.M` is the only applicable
|
|
|
|
member, the call `base(I3).M(0)` as written is an error, because `I2.M` does
|
|
|
|
not have an implementation in `I3`.
|
|
|
|
|
|
|
|
**Conclusion**
|
|
|
|
|
|
|
|
The decision from the previous meeting is affirmed and we conclude that the
|
|
|
|
lookup rules will not be changed. It's not clear what the new lookup rules
|
|
|
|
would be and it would be difficult to find when to apply them. In general,
|
|
|
|
we think having a single set of lookup rules will reduce confusion in an
|
|
|
|
already complicated feature area.
|
|
|
|
|
|
|
|
### Semantics of `base(T).Member`
|
|
|
|
|
|
|
|
We also affirm that for `base(T).Member`:
|
|
|
|
|
|
|
|
* `T` can be a class or interface
|
|
|
|
* All members are available on `base(T).Member` including fields
|
|
|
|
* A definition or implementation must exist in `T`
|
|
|
|
|
|
|
|
*Aside: The compiler will never emit a call to a member in metadata
|
|
|
|
that is inaccessible.*
|
|
|
|
|
|
|
|
This decision essentially falls out of existing binding rules for the given
|
|
|
|
expression.
|
|
|
|
|
|
|
|
### Accessibility in interfaces
|
|
|
|
|
|
|
|
We previously agreed to support at least `protected`, `private`, and `public`.
|
|
|
|
|
|
|
|
What about `internal`, `protected internal`, or `private protected`?
|
|
|
|
|
|
|
|
In addition, what is the meaning of `protected`?
|
|
|
|
|
|
|
|
**Conclusion**
|
|
|
|
|
|
|
|
Allow all accessibility. `protected` specifically seems useful for compatibility
|
|
|
|
with other languages which allow it, and to allow interfaces to create helper
|
|
|
|
methods for their derived implementations.
|
|
|
|
|
|
|
|
There are seem to be two possible definitions of `protected`: `protected`
|
|
|
|
members are visible only in deriving interfaces, or `protected` members are
|
|
|
|
visible in both deriving interfaces and implementing classes.
|
|
|
|
|
|
|
|
The preferred definition is `protected` members being visible in all deriving
|
|
|
|
interfaces and implementing classes.
|
|
|
|
|
|
|
|
### Accessibility of overriding interface members
|
|
|
|
|
|
|
|
There are two issues here: what is the language rule around calling
|
|
|
|
overriding implementations via `base` and how to implement that rule via
|
|
|
|
accessibility in the CLR.
|
|
|
|
|
|
|
|
We like the rule that you should always be able to call an overriding
|
|
|
|
implementation through `base()` as long as you can see the definition.
|
|
|
|
|
|
|
|
The problem is that the obvious implementation (copying the accessibility of
|
|
|
|
the definition) doesn't quite work with `InternalsVisibleTo` (which is
|
|
|
|
technically not described in the language).
|
|
|
|
|
|
|
|
Proposal 1:
|
|
|
|
|
|
|
|
If the implementing member is in the same assembly as the
|
|
|
|
definition we can copy the accessibility.
|
|
|
|
|
|
|
|
Proposal 2:
|
|
|
|
|
|
|
|
For all explicit interface implementations, emit the `protected`
|
|
|
|
accessibility.
|
|
|
|
|
|
|
|
**Conclusion**
|
|
|
|
|
|
|
|
The language rule is confirmed.
|
|
|
|
|
|
|
|
We think Proposal 2 works with the language rules, is simple, and doesn't
|
|
|
|
expose anything that we would regret. Let's go with that.
|