Building console apps using Visual Studio
The goal of this section is to showcase how to build a console app using Visual Studio.
If you do not have a Windows computer or want to use VS Code, then you can skip this section, since the code will be the same; just the tooling experience is different. However, I recommend that you review this section because it does explain some of the code and how top-level programs work, and that information applies to all code editors.
This section is also available in the GitHub repository (so it can be updated after publishing if needed) at the following link:
https://github.com/markjprice/cs13net9/blob/main/docs/code-editors/vs.md
If you want to see similar instructions for using Rider, they are available in the GitHub repository at the following link:
https://github.com/markjprice/cs13net9/blob/main/docs/code-editors/rider.md
Writing code using Visual Studio
Let’s get started writing code:
- Start Visual Studio.
- In the Create a new project dialog, select the C# language to filter the project templates, enter
console
in the Search for templates box, and then select Console App.
Make sure that you have chosen the cross-platform project template, not the one for .NET Framework, which is Windows-only, and the C# project template rather than another language, such as Visual Basic or TypeScript.
- Click Next.
- In the Configure your new project dialog, enter
HelloCS
for the project name,C:\cs13net9
for the location, andChapter01
for the solution name.
Screenshots of Visual Studio when creating new projects can be found in the GitHub repository at the following link: https://github.com/markjprice/cs13net9/blob/main/docs/ch01-project-options.md.
- Click Next.
- In the Additional information dialog, in the Framework drop-down list, note that your .NET SDK choices indicate if that version is Standard Term Support, Long Term Support, Preview, or Out of support, and then select .NET 9.0 (Standard Term Support).
You can install as many .NET SDK versions as you like. If you are missing a .NET SDK version, then you can install it from the following link: https://dotnet.microsoft.com/en-us/download/dotnet.
- Leave the checkbox labeled Do not use top-level statements clear. (Later in this chapter, you will create a console app that selects this option, so you will see the difference.)
- Leave the checkbox labeled Enable native AOT publish clear. You will learn what this option does in Chapter 7, Packaging and Distributing .NET Types.
- Click Create.
- If you cannot see Solution Explorer, then navigate to View | Solution Explorer.
- If code is not shown, then in Solution Explorer, double-click the file named
Program.cs
to open it, and note that Solution Explorer shows the HelloCS project, as shown in Figure 1.4:
Figure 1.4: Editing Program.cs in Visual Studio
- In
Program.cs
, note that the code consists of only a comment and a single statement, as shown in the following code:// See https://aka.ms/new-console-template for more information Console.WriteLine("Hello, World!");
This template uses the top-level program feature introduced in C# 9, which I will explain later in this chapter. As the comment in the code says, you can read more about this template at the following link: https://aka.ms/new-console-template.
- In
Program.cs
, modify line 2 so that the text that is being written to the console saysHello, C#!
.All code examples and commands that you must review or type are shown in plain text, so you will never have to read code or commands from a screenshot, like in Figure 1.4, which might be too small or too faint in print.
Compiling and running code using Visual Studio
The next task is to compile and run the code:
- In Visual Studio, navigate to Debug | Start Without Debugging.
Good Practice: When you start a project in Visual Studio, you can choose whether to attach a debugger or not. If you do not need to debug, then it is better not to attach one because attaching a debugger requires more resources and slows everything down. Attaching a debugger also limits you to only starting one project. If you want to run more than one project, each with a debugger attached, then you must start multiple instances of Visual Studio. In the toolbar, click the green outline triangle button (to the right of HelloCS in the top bar shown in Figure 1.5) to start without debugging, instead of the green solid triangle button (to the left of HelloCS in the top bar shown in Figure 1.5), unless you need to debug.
- The output in the console window will show the result of running your application, as shown in Figure 1.5:
Figure 1.5: Running the console app on Windows
- Press any key to close the console app window and return to Visual Studio.
- Optionally, close the Properties pane to make more vertical space for Solution Explorer.
- Double-click the HelloCS project, and note that the
HelloCS.csproj
project file shows that this project has its target framework set tonet9.0
, as shown in Figure 1.6. - In the Solution Explorer toolbar, toggle on the Show All Files button, and note that the compiler-generated
bin
andobj
folders are visible, as shown in Figure 1.6:
Figure 1.6: Showing the compiler-generated folders and files
Understanding the compiler-generated folders and files
Two compiler-generated folders were created, named obj
and bin
, as described in the following list:
- The
obj
folder contains one compiled object file for each source code file. These objects haven’t been linked together into a final executable yet. - The
bin
folder contains the binary executable for the application or class library. We will look at this in more detail in Chapter 7, Packaging and Distributing .NET Types.
You do not need to look inside these folders or understand their files yet (but feel free to browse around if you are curious).
Just be aware that the compiler needs to create temporary folders and files to do its work. You could delete these folders and their files, and they will be automatically recreated the next time you “build” or run the project. Developers often delete these temporary folders and files to “clean” a project. Visual Studio even has a command on the Build menu named Clean Solution that deletes some of these temporary files for you. The equivalent command with the CLI is dotnet clean
.
Understanding top-level programs
If you have seen older .NET projects before, then you might have expected more code, even just to output a simple message. This project has minimal statements because some of the required code is written for you by the compiler when you target .NET 6 or later.
If you had created the project with .NET SDK 5 or earlier, or if you had selected the checkbox labeled Do not use top-level statements, then the Program.cs
file would have more statements, as shown in the following code:
using System;
namespace HelloCS
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Hello, World!");
}
}
}
During compilation with .NET SDK 6 or later, all the boilerplate code to define the Program
class and its Main
method is generated and wrapped around the statements you write.
This uses a feature introduced in .NET 5 called top-level programs, but it was not until .NET 6 that Microsoft updated the project template for console apps to use top-level statements by default. Then, in .NET 7 and later, Microsoft added options to use the older style if you prefer:
- If you are using Visual Studio, select the checkbox labeled Do not use top-level statements.
- If you are using the
dotnet
CLI at the command prompt, add a switch:dotnet new console --use-program-main
Warning! One functional difference is that the auto-generated code does not define a namespace, so the Program
class is implicitly defined in an empty namespace with no name, instead of a namespace that matches the name of the project.
Requirements for top-level programs
Key points to remember about top-level programs include the following:
- There can be only one file like the file you use for top-level program code in a project.
- Any
using
statements must be at the top of the file. - If you declare any classes or other types, they must be at the bottom of the file.
- Although you should name the entry-point method
Main
if you explicitly define it, the method is named<Main>$
when created by the compiler.
Implicitly imported namespaces
The using System;
statement at the top of the file imports the System
namespace. This enables the Console.WriteLine
statement to work. But why do we not have to import it in our project?
The trick is that we still need to import the System
namespace, but it is now done for us using a combination of features introduced in C# 10 and .NET 6. Let’s see how:
- In Solution Explorer, expand the
obj
,Debug
, andnet9.0
folders, and open the file namedHelloCS.GlobalUsings.g.cs
. - Note that this file is automatically created by the compiler for projects that target .NET 6 or later and uses a feature introduced in C# 10, called global namespace imports, which imports some commonly used namespaces like
System
for use in all code files, as shown in the following code:// <autogenerated /> global using global::System; global using global::System.Collections.Generic; global using global::System.IO; global using global::System.Linq; global using global::System.Net.Http; global using global::System.Threading; global using global::System.Threading.Tasks;
- In Solution Explorer, click the Show All Files button to hide the
bin
andobj
folders.
I will explain more about the implicit imports feature in the next chapter. For now, just note that a significant change that happened between .NET 5 and .NET 6 is that many of the project templates, like the one for console apps, use new SDK and language features to hide what is really happening.
Revealing the hidden code by throwing an exception
Now let’s discover how the hidden code has been written:
- In
Program.cs
, after the statement that outputs the message, add a statement to throw a new exception, as shown in the following code:throw new Exception();
- In Visual Studio, navigate to Debug | Start Without Debugging. (Do not start the project with debugging, or the exception will be caught by the debugger!)
- The output in the console window will show the result of running your application, including that a hidden
Program
class was defined by the compiler, with a method named<Main>$
that has a parameter namedargs
to pass in arguments, as shown in Figure 1.7 and the following output:Hello, C#! Unhandled exception. System.Exception: Exception of type 'System.Exception' was thrown. at Program.<Main>$(String[] args) in C:\cs13net9\Chapter01\HelloCS\Program.cs:line 3
Figure 1.7: Throwing an exception to reveal the hidden Program.<Main>$ method
- Press any key to close the console app window and return to Visual Studio.
Revealing the namespace for the Program class
Now, let’s discover what namespace the Program
class has been defined within:
- In
Program.cs
, before the statement that throws an exception, add statements to get the name of the namespace of theProgram
class, and then write it to the console, as shown in the following code:string name = typeof(Program).Namespace ?? "<null>"; Console.WriteLine($"Namespace: {name}");
??
is the null-coalescing operator. The first statement means, “If the namespace of Program
is null
, then return <null>
; otherwise, return the name.” You will see more explanations of these keywords and operators throughout the book. For now, just enter the code and run it to see what it does.
Good Practice: Code editors have a feature named code snippets. These allow you to insert pieces of code that you commonly use, by typing a shortcut and pressing Tab twice. For example, in Visual Studio, to enter Console.WriteLine()
and leave the cursor in the middle of the parentheses ready for you to type what you want to output, type cw
, and then press Tab, Tab. Read the documentation for your code editor to learn how to insert code snippets using shortcuts.
- In Visual Studio, navigate to Debug | Start Without Debugging.
- The output in the console window will show the result of running your application, including that the hidden
Program
class was defined without a namespace, as shown in the following output:Namespace: <null>
- Press any key to close the console app window and return to Visual Studio.
Adding a second project using Visual Studio
Let’s add a second project to our solution to explore how to work with multiple projects:
- In Visual Studio, navigate to File | Add | New Project….
Warning! The above step adds a new project to the existing solution. Do NOT navigate to File | New | Project…, which instead is meant to be used to create a new project and solution (although the dialog box has a dropdown to choose to add to an existing solution too).
- In the Add a new project dialog, in Recent project templates, select Console App [C#], and then click Next.
- In the Configure your new project dialog, for Project name, enter
AboutMyEnvironment
, leave the location asC:\cs13net9\Chapter01
, and then click Next. - In the Additional information dialog, select .NET 9.0 (Standard Term Support) and select the Do not use top-level statements checkbox.
Warning! Make sure you have selected the Do not use top-level statements checkbox so that we get to see the older style of Program.cs
.
- Click Create.
- In the
AboutMyEnvironment
project, inProgram.cs
, note the statements to define a namespace that matches the project name, an internal class namedProgram
, and a static method namedMain
with a parameter namedargs
that returns nothing (void
), as shown in the following code:namespace AboutMyEnvironment { internal class Program { static void Main(string[] args) { Console.WriteLine("Hello, World!"); } } }
- In
Program.cs
, in theMain
method, replace the existingConsole.WriteLine
statement with statements to output the current directory, the version of the operating system, and the namespace of theProgram
class, as shown in the following code:Console.WriteLine(Environment.CurrentDirectory); Console.WriteLine(Environment.OSVersion.VersionString); Console.WriteLine("Namespace: {0}", typeof(Program).Namespace ?? "<null>");
- In Solution Explorer, right-click the Chapter01 solution, and then select Configure Startup Projects….
- In the Solution ‘Chapter01’ Property Pages dialog box, set Startup Project to Current selection, and then click OK.
- In Solution Explorer, click the
AboutMyEnvironment
project (or any file or folder within it), and note that Visual Studio indicates thatAboutMyEnvironment
is now the startup project by making the project name bold.
Good Practice: I recommend this way of setting the startup project because it then makes it very easy to switch startup projects by simply clicking a project (or any file in a project) to make it the startup project. Although you can right-click a project and set it as a startup project, if you then want to run a different project, you must manually change it again. Simply clicking anywhere in the project is easier. In most chapters, you will only need to run one project at a time. In Chapter 15, Building and Consuming Web Services, I will show you how to configure multiple startup projects.
- Navigate to Debug | Start Without Debugging to run the
AboutMyEnvironment
project, and note the result, as shown in the following output and Figure 1.8:C:\cs13net9\Chapter01\AboutMyEnvironment\bin\Debug\net9.0 Microsoft Windows NT 10.0.26100.0 Namespace: AboutMyEnvironment
Figure 1.8: Running a console app in a Visual Studio solution with two projects
Windows 11 is just branding. Its official name is Windows NT, and its major version number is still 10! But its patch version is 22000 or higher.
- Press any key to close the console app window and return to Visual Studio.
When Visual Studio runs a console app, it executes it from the
<projectname>\bin\Debug\net9.0
folder. It will be important to remember this when we work with the filesystem in later chapters. When using VS Code, or more accurately, thedotnet
CLI, it has different behavior, as you are about to see.