2020-04-22 18:39:24 +02:00
|
|
|
Extending Partial Methods
|
|
|
|
=====
|
|
|
|
|
|
|
|
## Summary
|
|
|
|
This proposal aims to remove all restrictions around the signatures of `partial`
|
|
|
|
methods in C#. The goal being to expand the set of scenarios in which these
|
|
|
|
methods can work with source generators as well as being a more general
|
|
|
|
declaration form for C# methods.
|
|
|
|
|
2020-07-30 20:39:47 +02:00
|
|
|
See also the [original partial methods specification](../../spec/classes.md#partial-methods).
|
2020-04-23 22:06:39 +02:00
|
|
|
|
2020-04-22 18:39:24 +02:00
|
|
|
## Motivation
|
|
|
|
C# has limited support for developers splitting methods into declarations and
|
|
|
|
definitions / implementations.
|
|
|
|
|
|
|
|
```cs
|
|
|
|
partial class C
|
|
|
|
{
|
|
|
|
// The declaration of C.M
|
|
|
|
partial void M(string message);
|
|
|
|
}
|
|
|
|
|
|
|
|
partial class C
|
|
|
|
{
|
|
|
|
// The definition of C.M
|
|
|
|
partial void M(string message) => Console.WriteLine(message);
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
One behavior of `partial` methods is that when the definition is absent then
|
|
|
|
the language will simply erase any calls to the `partial` method. Essentially
|
|
|
|
it behaves like a call to a `[Conditional]` method where the condition was
|
|
|
|
evaluated to false.
|
|
|
|
|
|
|
|
```cs
|
|
|
|
partial class D
|
|
|
|
{
|
|
|
|
partial void M(string message);
|
|
|
|
|
|
|
|
void Example()
|
|
|
|
{
|
|
|
|
M(GetIt()); // Call to M and GetIt erased at compile time
|
|
|
|
}
|
|
|
|
|
|
|
|
string GetIt() => "Hello World";
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
The original motivation for this feature was source generation in the form of
|
|
|
|
designer generated code. Users were constantly editing the generated code
|
|
|
|
because they wanted to hook some aspect of the generated code. Most notably
|
|
|
|
parts of the Windows Forms startup process, after components were initialized.
|
|
|
|
|
|
|
|
Editing the generated code was error prone because any action which caused the
|
|
|
|
designer to regenerate the code would cause the user edit to be erased. The
|
|
|
|
`partial` method feature eased this tension because it allowed designers to
|
|
|
|
emit hooks in the form of `partial` methods.
|
|
|
|
|
|
|
|
Designers could emit hooks like `partial void OnComponentInit()` and developers
|
|
|
|
could define declarations for them or not define them. In either case though
|
|
|
|
the generated code would compile and developers who were interested in the
|
|
|
|
process could hook in as needed.
|
|
|
|
|
|
|
|
This does mean that partial methods have several restrictions:
|
|
|
|
|
|
|
|
1. Must have a `void` return type.
|
2020-04-23 22:06:39 +02:00
|
|
|
1. Cannot have `out` parameters.
|
2020-04-22 18:39:24 +02:00
|
|
|
1. Cannot have any accessibility (implicitly `private`).
|
|
|
|
|
|
|
|
These restrictions exist because the language must be able to emit code when
|
|
|
|
the call site is erased. Given they can be erased `private` is the only possible
|
|
|
|
accessibility because the member can't be exposed in assembly metadata. These
|
|
|
|
restrictions also serve to limit the set of scenarios in which `partial` methods
|
|
|
|
can be applied.
|
|
|
|
|
|
|
|
The proposal here is to remove all of the existing restrictions around `partial`
|
|
|
|
methods. Essentially let them have `out`, non-void return types or any
|
|
|
|
type of accessibility. Such `partial` declarations would then have the added
|
|
|
|
requirement that a definition must exist. That means the language does not
|
|
|
|
have to consider the impact of erasing the call sites.
|
|
|
|
|
|
|
|
This would expand the set of generator scenarios that `partial` methods could
|
|
|
|
participate in and hence link in nicely with our source generators feature. For
|
|
|
|
example a regex could be defined using the following pattern:
|
|
|
|
|
|
|
|
```cs
|
|
|
|
[RegexGenerated("(dog|cat|fish)")]
|
|
|
|
partial bool IsPetMatch(string input);
|
|
|
|
```
|
|
|
|
|
|
|
|
This gives both the developer a simple declarative way of opting into generators
|
|
|
|
as well as giving generators a very easy set of declarations to look through
|
|
|
|
in the source code to drive their generated output.
|
|
|
|
|
|
|
|
Compare that with the difficulty that a generator would have hooking up the
|
|
|
|
following snippet of code.
|
|
|
|
|
|
|
|
```cs
|
|
|
|
var regex = new RegularExpression("(dog|cat|fish)");
|
|
|
|
if (regex.IsMatch(someInput))
|
|
|
|
{
|
|
|
|
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
Given that the compiler doesn't allow generators to modify code hooking up this
|
|
|
|
pattern would be pretty much impossible for generators. They would need to
|
|
|
|
resort to reflection in the `IsMatch` implementation, or asking users to change
|
|
|
|
their call sites to a new method + refactor the regex to pass the string literal
|
|
|
|
as an argument. It's pretty messy.
|
|
|
|
|
|
|
|
## Detailed Design
|
|
|
|
The language will change to allow `partial` methods to be annotated with an
|
|
|
|
explicit accessibility modifier. This means they can be labeled as `private`,
|
|
|
|
`public`, etc ...
|
|
|
|
|
|
|
|
When a `partial` method has an explicit accessibility modifier
|
|
|
|
though the language will require that the declaration has a matching
|
|
|
|
definition even when the accessibility is `private`:
|
|
|
|
|
|
|
|
```cs
|
|
|
|
partial class C
|
|
|
|
{
|
|
|
|
// Okay because no definition is required here
|
|
|
|
partial void M1();
|
|
|
|
|
|
|
|
// Okay because M2 has a definition
|
|
|
|
private partial void M2();
|
|
|
|
|
|
|
|
// Error: partial method M3 must have a definition
|
|
|
|
private partial void M3();
|
|
|
|
}
|
|
|
|
|
|
|
|
partial class C
|
|
|
|
{
|
|
|
|
private partial void M2() { }
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
Further the language will remove all restrictions on what can appear on a
|
|
|
|
`partial` method which has an explicit accessibility. Such declarations can
|
2020-04-23 22:06:39 +02:00
|
|
|
contain non-void return types, `out` parameters, `extern` modifier,
|
2020-04-22 18:39:24 +02:00
|
|
|
etc ... These signatures will have the full expressivity of the C# language.
|
|
|
|
|
|
|
|
```cs
|
|
|
|
partial class D
|
|
|
|
{
|
|
|
|
// Okay
|
|
|
|
internal partial bool TryParse(string s, out int i);
|
|
|
|
}
|
|
|
|
|
|
|
|
partial class D
|
|
|
|
{
|
|
|
|
internal partial bool TryParse(string s, out int i) { }
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
This explicitly allows for `partial` methods to participate in `overrides` and
|
|
|
|
`interface` implementations:
|
|
|
|
|
|
|
|
```cs
|
|
|
|
interface IStudent
|
|
|
|
{
|
|
|
|
string GetName();
|
|
|
|
}
|
|
|
|
|
|
|
|
partial class C : IStudent
|
|
|
|
{
|
|
|
|
public virtual partial string GetName();
|
|
|
|
}
|
|
|
|
|
|
|
|
partial class C
|
|
|
|
{
|
|
|
|
public virtual partial string GetName() => "Jarde";
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
The compiler will change the error it emits when a `partial` method contains
|
|
|
|
an illegal element to essentially say:
|
|
|
|
|
|
|
|
> Cannot use `ref` on a `partial` method that lacks explicit accessibility
|
|
|
|
|
|
|
|
This will help point developers in the right direction when using this feature.
|
|
|
|
|
|
|
|
Restrictions:
|
|
|
|
- `partial` declarations with explicit accessibility must have a definition
|
|
|
|
- `partial` declarations and definition signatures must match on all method
|
|
|
|
and parameter modifiers. The only aspects which can differ are parameter names
|
|
|
|
and attribute lists (this is not new but rather an existing requirement of
|
|
|
|
`partial` methods).
|
|
|
|
|
|
|
|
## Questions
|
|
|
|
|
|
|
|
### partial on all members
|
|
|
|
Given that we're expanding `partial` to be more friendly to source generators
|
|
|
|
should we also expand it to work on all class members? For example should we
|
|
|
|
be able to declare `partial` constructors, operators, etc ...
|
|
|
|
|
|
|
|
**Resolution**
|
|
|
|
The idea is sound but at this point in the C# 9 schedule we're trying to avoid
|
|
|
|
unnecessary feature creep. Want to solve the immediate problem of expanding
|
|
|
|
the feature to work with modern source generators.
|
|
|
|
|
|
|
|
Extending `partial` to support other members will be considered for the C# 10
|
|
|
|
release. Seems likely that we will consider this extension.
|
|
|
|
|
|
|
|
### Use abstract instead of partial
|
|
|
|
The crux of this proposal is essentially ensuring that a declaration has a
|
|
|
|
corresponding definition / implementation. Given that should we use `abstract`
|
|
|
|
since it's already a language keyword that forces the developer to think about
|
|
|
|
having an implementation?
|
|
|
|
|
|
|
|
**Resolution**
|
|
|
|
There was a healthy discussion about this but eventually it was decided against.
|
|
|
|
Yes the requirements are familiar but the concepts are significantly different.
|
|
|
|
Could easily lead the developer to believe they were creating virtual slots when
|
|
|
|
they were not doing so.
|