Skip to content

Conversation

@jnm2
Copy link
Contributor

@jnm2 jnm2 commented Dec 27, 2025

This code is accepted just fine:

partial class C
{
    async partial void M() { }

    partial void M();
}

And this is by design. It's not allowed for the async modifier to be on both parts. Nor would that make intuitive sense, since async is a keyword that only affects the body. It does not affect the signature.

partial class C
{
    async partial void M() { }

    // ❌ CS1994: The 'async' modifier can only be used in methods that have a body.
    async partial void M();
}

When C# 9 comes along, extern will have to be added as another exception for the same reason.

@jnm2
Copy link
Contributor Author

jnm2 commented Dec 27, 2025

@BillWagner @jskeet This looks like a candidate for merging without a meeting discussion.

@Nigel-Ecma
Copy link
Contributor

Nor would that make intuitive sense, since async is a keyword that only affects the body. It does not affect the signature.

Could not an implementation choose to invoke an async method differently than a non-async one? And user code surely does? So why does it only “affect the body”?

While I do not doubt that a certain compiler made this choice intentionally, I’m unsure that an implementation which choose to allow it should be deemed to be in error.

@jnm2
Copy link
Contributor Author

jnm2 commented Dec 27, 2025

Could not an implementation choose to invoke an async method differently than a non-async one? And user code surely does? So why does it only “affect the body”?

User code does not invoke an async method differently from a non-async one. Nor would an implementation invoke it differently. The effect of the async keyword is invisible to callers of the method.

What the async keyword does in a nutshell is rewrite the body of the method to use a state machine to produce the Task object (or some task-like object) which is returned by the method, rather than having the user manually return some Task object. The effect of async from a language user perspective is not "up to the implementation" but is rather a semantic mode switch that the user understands.

Either way, the caller invokes the method the same way (a direct method call), and it sees only the Task object which is returned the same way (from that direct method call). The signature is entirely unaffected by the async modifier by design. It can be swapped in and out to suit the method implementer.

@jnm2
Copy link
Contributor Author

jnm2 commented Dec 27, 2025

And then, given that the async keyword affects only the body, it would be nonsensical to allow the keyword on a method which has no body. So an implementation which ignored this nonsensical placement should be in error. The thing which the keyword refers to is not there.

@Nigel-Ecma
Copy link
Contributor

What the async keyword does in a nutshell is rewrite the body of the method to use a state machine to produce the Task object (or some task-like object) which is returned by the method, rather than having the user manually return some Task object.

Where in your code:

async partial void M() { }

is this “Task object” you mention specified, the signature seems to say it returns void.

Is it perhaps indicated by the async, which thereby informs that calling this method should be by way of something like an await and it returns a Task object?

So is this method perhaps not called like one taking no arguments and returning nothing as its “signature” might suggest?

Anyway, neither of us should be here today! Happy New Year, see you in 2026 🙂

@jnm2
Copy link
Contributor Author

jnm2 commented Dec 27, 2025

Oops, I forgot that part. With async void all the same things hold true about its lack of effect on the signature:

  • async enables the await keyword within the body and rewrites the body as a state machine, for example marshaling exceptions elsewhere
  • async may be added or removed without affecting the caller in any way- the caller does not react to its presence

But the caller cannot observe when any asynchronous work is completed due to the return type being void. The method is called like one taking no arguments and returning nothing, since that is what it is.

I appreciate that ☺️ Happy 2026 to you as well!

@BillWagner BillWagner merged commit dda6edd into draft-v8 Jan 14, 2026
9 checks passed
@jnm2 jnm2 deleted the partial_method_modifier_exception branch January 14, 2026 21:46
BillWagner added a commit that referenced this pull request Jan 16, 2026
* Bump Microsoft.NET.Test.Sdk and 2 others (#1526)

Bumps Microsoft.NET.Test.Sdk from 17.14.1 to 18.0.1
Bumps Newtonsoft.Json from 13.0.3 to 13.0.4
Bumps xunit.runner.visualstudio from 3.1.4 to 3.1.5

---
updated-dependencies:
- dependency-name: Microsoft.NET.Test.Sdk
  dependency-version: 18.0.1
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: dotnet
- dependency-name: Newtonsoft.Json
  dependency-version: 13.0.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: dotnet
- dependency-name: xunit.runner.visualstudio
  dependency-version: 3.1.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: dotnet
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* Bump Microsoft.NET.Test.Sdk, NUnit.Analyzers and NUnit3TestAdapter (#1525)

Bumps Microsoft.NET.Test.Sdk from 17.14.1 to 18.0.1
Bumps NUnit.Analyzers from 4.10.0 to 4.11.2
Bumps NUnit3TestAdapter from 5.1.0 to 6.0.1

---
updated-dependencies:
- dependency-name: Microsoft.NET.Test.Sdk
  dependency-version: 18.0.1
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: dotnet
- dependency-name: NUnit.Analyzers
  dependency-version: 4.11.2
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: dotnet
- dependency-name: NUnit3TestAdapter
  dependency-version: 6.0.1
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: dotnet
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* Address remaining nullable reference type issues (#1515)

* Warning on lifted conversion w/ null

Fixes item 2 in #1228

* Specify default null state of default

Fixes the 3rd and 4th item in the list.

* Update standard/types.md

Co-authored-by: Joseph Musser <me@jnm2.com>

* Apply suggestions from code review

Co-authored-by: Joseph Musser <me@jnm2.com>

---------

Co-authored-by: Joseph Musser <me@jnm2.com>

* [create-pull-request] automated change (#1531)

Co-authored-by: BillWagner <493969+BillWagner@users.noreply.github.com>

* Add an exception to the requirement that partial method declarations have the same modifiers (#1521)

* Add an exception to the requirement that partial method declarations have the same modifiers

* Update standard/classes.md

---------

Co-authored-by: Jon Skeet <jonskeet@google.com>

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Joseph Musser <me@jnm2.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: BillWagner <493969+BillWagner@users.noreply.github.com>
Co-authored-by: Jon Skeet <jonskeet@google.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants