csharplang icon indicating copy to clipboard operation
csharplang copied to clipboard

[Proposal]: Attributes on Main for top level programs

Open jkotas opened this issue 2 years ago • 24 comments

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

jkotas avatar Aug 13 '21 22:08 jkotas

cc @jaredpar

jkotas avatar Aug 13 '21 22:08 jkotas

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.

333fred avatar Aug 14 '21 00:08 333fred

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?

jkotas avatar Aug 14 '21 00:08 jkotas

Another alternative that I personally like is entrypoint, as I think it communicates the idea a bit better.

davidwengier avatar Aug 14 '21 00:08 davidwengier

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.

333fred avatar Aug 14 '21 01:08 333fred

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.

RikkiGibson avatar Aug 14 '21 01:08 RikkiGibson

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

CyrusNajmabadi avatar Aug 14 '21 01:08 CyrusNajmabadi

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.

jkotas avatar Aug 14 '21 02:08 jkotas

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

RussKie avatar Aug 14 '21 07:08 RussKie

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 (or All) to the main method.

chsienki avatar Feb 11 '22 00:02 chsienki

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?

yaakov-h avatar Feb 11 '22 03:02 yaakov-h

[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 avatar Feb 11 '22 15:02 AlekseyTs

@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?

Neme12 avatar Jun 07 '22 15:06 Neme12

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.

AlekseyTs avatar Jun 07 '22 15:06 AlekseyTs

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?

MSDN-WhiteKnight avatar Jun 08 '22 09:06 MSDN-WhiteKnight

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.

Neme12 avatar Jun 08 '22 13:06 Neme12

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.

Neme12 avatar Jun 08 '22 13:06 Neme12

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.

RussKie avatar Jun 09 '22 02:06 RussKie

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 avatar Aug 18 '22 07:08 Sour-Codes

@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.

svick avatar Aug 18 '22 11:08 svick

You can also do:

[RequiresPreviewFeatures] 
partial class Program { }

333fred avatar Aug 18 '22 14:08 333fred

@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>.

Sour-Codes avatar Aug 18 '22 15:08 Sour-Codes

@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

Sour-Codes avatar Aug 18 '22 15:08 Sour-Codes

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.

jrmoreno1 avatar Sep 05 '22 14:09 jrmoreno1