Tuesday, December 17, 2019

C# 8 Interfaces: Static Main -- Why Not?

When taking a closer look at C# 8 interfaces, we come across static members. Last time we looked at using static fields and methods in an interface. But things get stranger. An interface can have a "static Main" method, which means that this is a valid application:

Okay, so there's technically more to it than this, but still: Yikes!
A static Main method lets us put the entry point to an application in an interface.
Let's take a look at this example to see what it means.

The code for this article is available on GitHub: jeremybytes/interfaces-in-csharp-8. Specifically, this uses the "StaticMain" project.

Note: this article uses C# 8 features which are not available in .NET Framework 4.8. For these samples, I used Visual Studio 16.4.1 and .NET Core 3.1.100.

Static Main
The static Main method is the entry point to an application. In a C# console application, this is generally in the "Program" class. And if you create a new console application using the .NET Core template, you get the following code:

Personal Note: One really sad thing about this template is that it fills in the "Hello World!" for you. So this takes all the fun out of building your first application.

The same is true of ASP.NET Core web applications and APIs. Here's the "Program" class that was created using the "webapi" template:

There is a bit more to the "Program" class than what is seen here, but the "Main" method is the entry point. When we start up the application, it runs "Main".

Static Main in an Interface
With C# 8 we can have static members in an interface. We can also create a "static Main" method in an interface.

Here is an interface that has a static Main method: "IHelloWorld" (from the IHelloWorld.cs file in the StaticMain project on GitHub):

The "Main" method has the same signature as the prior samples from a console application and an API application.

But instead of this method being in the Program class, it is in an interface.

The Shortest Program Class
When we have an interface with a static Main method, it leads to the world's shortest Program class (from the Program.cs file):

This is a valid C# console application (and it actually does something).

Running the Application
Let's see it work. From the command line (open at the project folder), we can type "dotnet run" to run the application.

And if we pass in a command line argument, we see that also works.

Note: When running a console application (or other application) with "dotnet run", we can pass arguments through by adding 2 dashes (--). Everything after those 2 dashes will show up in the "args" parameter on the "Main" method.

So we have a working application that has an empty body for the "Program" class.

"Program" is not Needed
So the "Program" class is empty, and it isn't doing any thing. Do we need it?

No, we don't.

If we remove the "Program" class entirely, the console application still works. That is because the "static Main" method is the entry point to the application; the "Program" class is not the entry point. The "Program" class is used as a convention, but we can use a different class (or interface) as the entry point to the application.
Developers expect to see the entry point inside the Program class. 
Keep that in mind if you are considering something different.

Multiple Entry Points
If we create another interface that has a "static Main" method, we get a compiler error. Here's the message:

"Program has more than one entry point defined. Compile with /main to specify the type that contains the entry point."

This restriction is not new; we've always had this if we tried to define multiple entry points. The difference is that it can apply to interfaces now, too.

This also means that if we have a "static Main" in the Program class, we cannot also have one in an interface (unless we use the compiler switch mentioned in the error message, and that's beyond the scope of this article).

Sure. Why Not?
As mentioned in previous articles (Static Members and Public, Private, and Protected Members), I'm still processing these changes and how they should influence my code.

Right now, I'm still holding onto the idea that interfaces are abstractions. But when we're talking about static members, we really lose that. So if we're talking about putting the entry point of an application into an interface... Why not?

Unfortunately, that is the design decision that came from Microsoft. If you check the Language Design Notes from Oct 17, 2018 (https://github.com/dotnet/csharplang/blob/master/meetings/2018/LDM-2018-10-17.md#main-in-an-interface), you'll find this gem:

"Main in an interface? No reason why not."

Translation: "No point in steering now."

Just Because You Can...
Just because you can do something doesn't mean that you should do something. I'm pretty hard-pressed to come up with a scenario where the entry point of an application would need to come from an interface. (I'll just brace for everyone supplying me with edge-case examples.)

The deeper I get into the changes to interfaces, the more concerned I get. I think that we've lost the meaning for what an interface is.

But there is nothing that forces us to use these features. So if we make the decision, we can continue to use interfaces as abstractions in our code. Even if we do this, it's important to understand what is possible because we may end up using libraries that *do* use these new features. I'm still hoping for the best at this point.

Happy Coding!

No comments:

Post a Comment