How to Add Code Outside the Scope of Main When Using C# 9 Top Level Statements

How do I add code outside the scope of Main when using C# 9 Top Level Statements?

For .Net 5:

When you use the Top-Level Program feature of C# 9, you give up the ability to put anything outside the Main method scope. Fields, properties, attributes on the Main method or Program class, setting the namespace, changing the class name, etc are all no longer available (the only exception is "importing" namespaces with using lines).

If that limitation doesn't work for you, don't use the feature.


For .Net 6 and higher, use dongus's answer.

Attributes on C#9 top level statements file

Assuming you want to set an assembly-wide attribute, then it's the same as it was before C# 9.0. You're missing the assembly: keyword.

https://learn.microsoft.com/en-us/dotnet/standard/assembly/set-attributes

Change your code to add the assembly: keyword, like so:

[assembly: SuppressMessage("StyleCop.CSharp.LayoutRules", "SA1516:ElementsMustBeSeparatedByBlankLine", Justification = "Reviewed.")

If you want to add the attribute to the implicit entrypoint method (aka Program.Main) or its parent type class Program then you cannot, simply because C# 9.0's top-level-statements design does not allow programs to name or reference the method at all.

This is mentioned, briefly, in the documentation on top-level methods (emphasis mine):

https://learn.microsoft.com/en-us/dotnet/csharp/fundamentals/program-structure/top-level-statements#implicit-entry-point-method

Implicit entry point method

The compiler generates a method to serve as the program entry point for a project with top-level statements. The name of this method isn't actually Main, it's an implementation detail that your code can't reference directly. The signature of the method depends on whether the top-level statements contain the await keyword or the return statement.

...in which case you'll need to change your code back to using a traditional explicit Main method:

[SuppressMessage("SomeClassRule", "", Justification = "It's my computer")]
public static class Program
{
[SuppressMessage("SomeMethodRule", "", Justification = "It's my computer")]
public static async Task<Int32> Main( String[] args )
{
return 0;
}
}

However..., as you're wanting to use [SuppressMessage] on the Main method to suppress StyleCop warnings, you need to be made-aware of the fact that StyleCop (as with other static-analysis tools in .NET) now respect .editorconfig and #pragma warning disable|restore to suppress or disable warnings and checks.

Here's a quick comparison of #pragma vs [SuppressMessage]: when a technique has a "better" feature it's marked in bold:





































#pragma warning disable[SuppressMessage]
Compiled into output assembly, growing your DLL size from all the constant strings and potentially exposing internal development details to external usersNoYes
Explicit Justification fieldNo

But you can add an explanatory comment on the same line.
Yes
Explicit target field for disambiguationNoYes
Requires modern versions of Visual Studio (2017 or 2019+)YesNo
GranularitySource code line

But if the same warning appears 2 or more times on the same source code line you cannot disambiguate
Discrete .NET assembly objects (types, parameters, fields, etc - but not per-line)

C# - Location of using statements

Putting "using" at the top of the files is the default way of Visual Studio. However, the recommended approach is putting the "using" statements inside of the namespace. Even MS's stylecop catches this and says the default way of VS is wrong.

Both techniques work fine.

StyleCop Rule says:
Placing multiple namespace elements
within a single file is generally a
bad idea, but if and when this is
done, it is a good idea to place all
using directives within each of the
namespace elements, rather than
globally at the top of the file. This
will scope the namespaces tightly, and
will also help to avoid the kind of
behavior described above.

It is important to note that when code
has been written with using directives
placed outside of the namespace, care
should be taken when moving these
directives within the namespace, to
ensure that this is not changing the
semantics of the code. As explained
above, placing using-alias directives
within the namespace element allows
the compiler to choose between
conflicting types in ways that will
not happen when the directives are
placed outside of the namespace.

Here's some links for further review:

  • Should 'using' statements be inside or outside the namespace?
  • Is sa1200 All using directives must be placed inside the namespace (StyleCop) purely cosmetic?
  • http://www.hanselman.com/blog/BackToBasicsDoNamespaceUsingDirectivesAffectAssemblyLoading.aspx
  • http://blogs.msdn.com/sourceanalysis/pages/sa1200-usingdirectivesmustbeplacedwithinnamespace.aspx

Should 'using' directives be inside or outside the namespace in C#?

There is actually a (subtle) difference between the two. Imagine you have the following code in File1.cs:

// File1.cs
using System;
namespace Outer.Inner
{
class Foo
{
static void Bar()
{
double d = Math.PI;
}
}
}

Now imagine that someone adds another file (File2.cs) to the project that looks like this:

// File2.cs
namespace Outer
{
class Math
{
}
}

The compiler searches Outer before looking at those using directives outside the namespace, so it finds Outer.Math instead of System.Math. Unfortunately (or perhaps fortunately?), Outer.Math has no PI member, so File1 is now broken.

This changes if you put the using inside your namespace declaration, as follows:

// File1b.cs
namespace Outer.Inner
{
using System;
class Foo
{
static void Bar()
{
double d = Math.PI;
}
}
}

Now the compiler searches System before searching Outer, finds System.Math, and all is well.

Some would argue that Math might be a bad name for a user-defined class, since there's already one in System; the point here is just that there is a difference, and it affects the maintainability of your code.

It's also interesting to note what happens if Foo is in namespace Outer, rather than Outer.Inner. In that case, adding Outer.Math in File2 breaks File1 regardless of where the using goes. This implies that the compiler searches the innermost enclosing namespace before it looks at any using directive.

How to declare global variable in Program cs and use it in controllers in .NET 6.0 Web Api

Starting C# 9, we don't need to explicitly mention the Main method in Program.cs file as we can use the top-level statements feature. However, it doesn't mean that we shouldn't use the default Program class in the created file at all. In your case, you have a need to define the static/const property so you can change the newly created structure into the old one.

namespace WebApplication;

public class Program
{
public static string Test { get; private set; }
public static void Main(string[] args)
{
var builder = WebApplication.CreateBuilder(args);

Program.Test = "approach1";
builder.Services.Configure<MyOptions>(x => x.Test = "approach2");
///
}

public class MyOptions
{
public string Test { get; set; }
}

I assumed that you have a need to set the value to the Program.Test field during runtime, so in the first approach, I used the static field with a private set; accessor instead of the constant.

In the second approach, I used the C# options feature to configure the MyOptions.Test field value, this will be very flexible and useful to write unit tests later. But, you need to inject the MyOptions class wherever is required.

In the below controller template, I specified how to access the configured values at Program.cs file, inside the Get method

public class TestController : ControllerBase
{
private readonly MyOptions _myOptions;

public TestController (IOptions<MyOptions> myOptions)
{
_myOptions = myOptions.Value;
}

public IActionResult Get()
{
string test1 = Program.Test;
string test2 = _myOptions.Test;
///
}

}



Related Topics



Leave a reply



Submit