csharplang
csharplang copied to clipboard
[Proposal]: Attributes on Main for top level programs
Attributes on Main for top level programs
- [x] Proposed
- [ ] Prototype: Not Started
- [ ] Implementation: Not Started
- [ ] Specification: Not Started
Summary
Allow attaching attributes to Main method in top level programs.
Motivation
The primary motivation for this feature is tenabling top level programs for WinForms (see https://github.com/dotnet/designs/blob/main/accepted/2021/winforms/streamline-application-bootstrap.md). WinForms programs require the Main method to be annotated with [STAThread]
.
Detailed design
Add main
as the new global attribute target (assembly
and module
are the existing global attribute targets). Attribute specified with this target will be attached to the program Main method.
Example: [main: STAThread]
The main attribute target can be used in any compilation unit. In particular, it does need to be in the same compilation file as the top level program. This is required to enable the streamlined WinForms top level programs - the attribute will be generated by source generator.
It is an error to use the main
global attribute target in libraries.
Drawbacks
Alternatives
The alternative is to pass the information that is encoded via attributes on Main method via alternative channel in top level programs (e.g. attributes on assembly or config file) and modify runtime, libraries and tools to look for this information in the alternative location in addition to the existing location.
Unresolved questions
Can this be used to attach attributes to Main method even in non-top-level programs?
Design meetings
- https://github.com/dotnet/csharplang/blob/main/meetings/2021/LDM-2021-09-22.md#attributes-on-main-for-top-level-programs
- https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-01-24.md#attributes-on-main-for-top-level-programs
- https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-03-09.md#main-attributes
cc @jaredpar
Another alternative would be to simply use the method
target here, and make it an error if it's not in the same location as the top-level statements.
make it an error if it's not in the same location as the top-level statements.
What do you mean by "same location" in this context?
Another alternative that I personally like is entrypoint
, as I think it communicates the idea a bit better.
make it an error if it's not in the same location as the top-level statements.
What do you mean by "same location" in this context?
Same file, likely above the statements.
Putting it directly above the statements would syntactically attach the attributes to the first top-level statement, which seems like a good thing to me. At that point it’s not clear if it’s necessary to include a ‘method’/‘main’/’entrypoint’ target, although it may be preferred for clarity. Plus we may want to reserve the meaning of an attribute on a statement for some purpose in the future.
I would have these parse like assembly level attributes. Those don't attach to the namespace member decl, they go into their own section of the tree. In this case I would not have these attach to a statement
Same file, likely above the statements.
It is important that this attribute can be generated using source generator and thus live in a separate file. If it is not possible to generate this attribute using source generator, this feature loses a lot of its appeal for streamlined WinForms top level programs.
I'm currently emitting Thread.CurrentThread.SetApartmentState
for top level programs:
https://github.com/dotnet/winforms/blob/65e50657278f6a3af5ccb7ac6def8d5baa5dd490/src/System.Windows.Forms.Analyzers.CSharp/src/System/Windows/Forms/Generators/ApplicationConfigurationInitializeBuilder.cs#L91-L98
There are several open questions on this feature. For the purposes of the prototype we're going with the following designs, but will revisit them in a future LDM to determine their final answers.
Syntax: Should it be [main:
, [entrypoint:
or something else?
- We'll use
[main:
for now.
Scope: Should you be able to use [main:
attributes in other files? Should they apply to regular entrypoints or just top level programs?
-
[main:
can be specified in any file -
[main:
applies to any entrypoint, simple program or not
Targets: Should we introduce a corresponding AttributeTargets.Main
that allows an attribute to specify that it only applies to main methods?
- For now, no. We won't introduce a new attribute target. Users can apply any regular
Method
targeted attribute (orAll
) to the main method.
Interesting approach on those designs.
Does this mean that, completely hypothetically, the WinForms SDK could define [main: STAThread]
itself rather than requiring the developer to include that on their Main()
function?
[main: applies to any entrypoint, simple program or not
This is going to be very tricky to implement. We determine the entry-point very late and now we will need to figure this out just to be able to bind attributes. I am not sure if the complexity worth it.
@AlekseyTs Couldn't attributes with the main
target also be bound later in the pipeline, after the entry point is determined? Or alternatively, the attribute type and constructor could be bound as usual along with all other attributes, but only determine the method that it actually applies to later, after the entry point is determined?
Our current model is to be able to bind attributes related to a specific symbol on demand, pretty much any time. Today, all such attributes are syntactically related to symbol's declaration. All attributes are supposed to be returned through public API (GetAttributes
), we do not control at which point in time a consumer is going to call it. Of course, things would be easier, if the compiler could fully control all the timing.
The main attribute target can be used in any compilation unit. In particular, it does need to be in the same compilation file as the top level program. This is required to enable the streamlined WinForms top level programs - the attribute will be generated by source generator.
Is this part really required? The STA/MTA choice affects how threading works in COM interop and troubleshooting related issues, so it's better to have STAThread attribute visible next to the code it applies to. In case of WinForms it's always there and it does not matter, but for console apps i'd like to always determine quickly whether it enables STA.
Also, would WinForms benefit much from auto-generating STAThread attribute only? I think the whole Program.cs in WinForms apps is a boilerplate that people usually don't modify manually. Could WinForms work like WPF, where user sets startup window declaratively, and entry point is synthesized automatically with correct attributes?
I don't think most WinForms developers, or developers in general, use COM interop, or even know what it is. For the majority of devs, that attribute is just some weird funky thing that you're not supposed to touch.
But I agree that for WinForms, the whole entry point is something that could be generated and that people usually don't touch and aren't interested in.
Visual Basic hides the entry point with most configurations done via the Application Framework. That makes most (if not all) VB developers unaware of how the application is configured, and that creates frictions. So hiding the Program.cs or ther Main
method away from developers would be a bad move IMO. Also, whilst some app developers may choose not to modify the content of the default Main
method, others do modify it quite extensively, after all this is the place to bootstrap the applications before any UI is shown to the user.
I don't think most WinForms developers, or developers in general, use COM interop, or even know what it is.
I think you'd be surprised.
Kicking up the dust about this.
The comments from the LDM 2022-03-09 meeting focused on the [STAThread]
for WinForms and the unlikelihood that users need to apply it. A better argument for attribute usage on top-level statements is support for static abstract members in interfaces, since you have to apply [RequiresPreviewFeatures]
to any member (or apply to the type declaration for usage throughout the type) that eventually relies on a call to a static abstract
member within its block. This includes main
as well. Realizing that this hits hardest when utilizing Generic Math previews (.NET 6 and .NET 7 Preview 5) as it's heavily dependent on static abstract
declarations in many of its interfaces.
Found this thread and sharing this as I am writing a program now which is reporting CA2252: Using '<member>' requires opting into preview features
and was confused as to how to apply [RequiresPreviewFeatures]
to top-level main
.
Guess I'll revert to the traditional Program.Main
style statements until this is figured out.
@Sour-Codes The recommended way to enable preview features is to do it for the whole assembly by adding <EnablePreviewFeatures>true</EnablePreviewFeatures>
to your csproj.
You can also do:
[RequiresPreviewFeatures]
partial class Program { }
@svick, this approach works if you're expecting to use preview features throughout one project. My scenario is that my library also utilizes <GenerateRequiresPreviewFeaturesAttribute>false</GenerateRequiresPreviewFeaturesAttribute>
to not force dependent projects that aren't using preview features to be forced to apply the <EnablePreviewFeatures>true</EnablePreviewFeatures>
, as suggested in Building a library that offers preview features.
<GenerateRequiresPreviewFeaturesAttribute>false</GenerateRequiresPreviewFeaturesAttribute>
will require dependent projects to annotate with [RequiresPreviewFeatures]
even if its corresponding csproj has <EnablePreviewFeatures>true</EnablePreviewFeatures>
.
@333fred Works like a charm! 🎉🥳I missed the memo that top-level statements creates a partial class Program { }
behind the scenes somewhere.
#if NET6_0_OR_GREATER
using System.Runtime.Versioning;
[RequiresPreviewFeatures]
partial class Program
{
}
#endif
I don't think most WinForms developers, or developers in general, use COM interop, or even know what it is.
Can’t speak for others, but when I was doing VB WInForms I was aware of and using both…and have no reason to expect that the application’s aren’t still in use, so whoever is maintaining them now probably is as well.