C# & .NET (C# 14, .NET 10 LTS) Module 1 – Start here: C#, .NET and your first programs
Your first C# program as a file-based app
Write a one-file C# program and run it with dotnet run, pass it arguments, return an exit code, see the Main method it becomes, and run C# as a script.
What you will learn
- Write and run a one-file C# program with dotnet run, and pass it arguments
- Explain top-level statements, implicit usings and the Main method that the compiler writes for them
- Set the exit code of a program with return, and read it in the shell
- Run a C# file as a script on macOS or Linux with a shebang line
Before you start
What you’ll learn · Before you start
What you will learn
- Write and run a one-file C# program with dotnet run, and pass it arguments
- Explain top-level statements, implicit usings and the Main method that the compiler writes for them
- Set the exit code of a program with return, and read it in the shell
- Run a C# file as a script on macOS or Linux with a shebang line
Before you start
On this page
The smallest C# program is one line in one file, and since .NET 10 you need nothing else to run it: no project file,
no solution, no Main method. dotnet run hello.cs builds the file and runs it in one step, and on macOS or Linux a
single line at its top even lets you run it by name, like a shell script.
This lesson writes a program that greets the people named on its command line, shows what the compiler turns its statements into, ends a program with an exit code: The whole number a program hands back to the shell or the program that started it when it ends. 0 means success; any other value means some kind of failure. the shell can test, and runs a C# file as a script that counts the lines of its input.
One .cs file is a whole program for dotnet run
A file-based app: A C# program in a single .cs file that dotnet run builds and runs without a project file. The SDK generates the project behind it, new in .NET 10. is a program in a single .cs file. This one prints a greeting, then
every argument it was given. Run it with two names:
// A whole C# program: no class, no Main method and no using directive.
Console.WriteLine("Namaste from C#!");
Console.WriteLine($"You passed {args.Length} arguments.");
for (int i = 0; i < args.Length; i++)
{
Console.WriteLine($" args[{i}] = {args[i]}");
} Output
Namaste from C#! You passed 2 arguments. args[0] = Asha args[1] = Ravi
Recorded with .NET SDK 10.0.401 (runtime 10.0.12) on macOS 26 arm64. To run it yourself: dotnet run hello.cs Asha Ravi
The command under the output, dotnet run hello.cs Asha Ravi, built and ran the file, and dotnet hello.cs Asha Ravi
is a shorter way to do the same. The two names reached the program as args, an array of strings. When an argument
looks like an option of dotnet itself, such as --help, put -- before your arguments, as in
dotnet run hello.cs -- --help: everything after -- goes to the program untouched.
dotnet run is not an interpreter. Behind it the SDK writes a project for the file, restores what it needs, compiles
it and runs the result, and it keeps the build for next time. Step through a first run and a second one:
The drawing shows the whole scene; its 6 steps are listed below.
Every step as text
- Read the file. dotnet run hello.cs reads the file, including any #: lines at its top that configure the build. hello.cs has none.
- A project you never see. The SDK writes a project for the file in memory: an executable for net10.0 with nullable checks and implicit usings switched on, as a new console project has, plus a few settings of its own that the next lesson explains.
- Restore. Restore works out the NuGet packages the project needs. A plain file needs none of its own, but the settings of file-based apps can add some, which the next lesson shows.
- Build into a cache. The program is built into a folder of its own: under the temporary folder on Windows, under Library/Application Support/dotnet/runfile in your home folder on a Mac, named after the file and a hash of its path.
- Run it. The SDK runs the program it built, hands it the arguments you passed, and returns the program's exit code as its own.
- The second run. Run the same file again, unchanged, and the SDK finds its earlier build still valid, so it skips the project, the restore and the build. A change to the file, to its directives or to the SDK makes it build again.
Because the build is cached, the first run of a new file takes a few seconds and an unchanged file starts much faster
the next time. dotnet clean file-based-apps deletes cached builds that have not been used for 30 days.
Running a file inside a project folder
If the current folder holds a project file, dotnet run hello.cs runs that project and passes hello.cs to it as
an argument, which the SDK keeps for compatibility with older versions. Use dotnet run --file hello.cs, or keep
file-based apps in a folder without a project.
Checkpoint
Check: Running one file with dotnet run
Top-level statements become a Main method that the compiler writes
hello.cs has no class and no Main method, yet every .NET program starts in a method. Its code is made of
top-level statements: Code written directly in a file, outside any class or method. The compiler puts it into a method of a class named Program, which becomes the program's entry point.: statements written straight into the file, which the
compiler places in a method of a class named Program that it writes for you. This program uses await and return
at the top level, and asks .NET what the compiler made of it:
using System.Reflection;
// Top-level statements that use await and return: the compiler puts them in a method of a class named Program.
MethodInfo entry = Assembly.GetEntryAssembly()!.EntryPoint!;
Console.WriteLine($"Entry point: {entry.DeclaringType!.Name}.{entry.Name}, returns {entry.ReturnType.Name}");
var methods = typeof(Program).GetMethods(BindingFlags.Static | BindingFlags.NonPublic).Select(m => m.Name);
Console.WriteLine($"Methods the compiler wrote: {string.Join(", ", methods)}");
List<string> missing = ["config.json", "data.csv"]; // List<T> without a using directive: an implicit using
await Task.Delay(10); // await at the top level
Console.WriteLine($"{missing.Count} input files missing, so the exit code is 3");
return 3; // the exit code of the process Output (exit status 3)
Entry point: Program.<Main>, returns Int32 Methods the compiler wrote: <Main>$, <Main> 2 input files missing, so the exit code is 3
Recorded with .NET SDK 10.0.401 (runtime 10.0.12) on macOS 26 arm64. To run it yourself: dotnet run entry.cs
The output names two methods. <Main>$ holds your statements; because they use await, it is an async method that
returns a Task<int>. The entry point: The method that the runtime calls when a program starts, usually Main. With top-level statements the compiler writes it for you. that the runtime calls is the second method,
<Main>, which waits for the first and returns its int. The names contain < and > so that no C# code can
declare or call them. The shape of the generated method depends on what your statements use:
| Top-level code contains | The generated method returns |
|---|---|
neither await nor return |
void |
return with a value |
int |
await |
Task |
await and return with a value |
Task<int> |
The program also used List<string> and Task without a single using line for their namespaces. Those are
implicit usings: Global using directives that the SDK adds to a project, so its files can use types of namespaces such as System, System.IO and System.Linq without writing using lines.: the SDK adds global using directives for System,
System.Collections.Generic, System.IO, System.Linq, System.Net.Http, System.Threading and
System.Threading.Tasks. A file with top-level statements may still start with using lines of its own, like the
using System.Reflection; above, and may declare classes and records after its statements, never before them.
Top-level statements in two files
A program has one entry point, so only one file of a project may hold top-level statements. In a file-based app the other files it includes may declare types, never statements; the next lesson splits a program that way.
Checkpoint
Check: Top-level statements and implicit usings
Arguments come in through args, and the exit code goes out through return
A command-line program talks to the shell in two directions: args brings the words typed after its name, and the
exit code reports how it ended. By convention 0 means success and any other number a kind
of failure, which is how scripts and build servers decide whether to go on. entry.cs above ended with return 3, and
its run record shows exit status 3. Step through how the two travel:
The drawing shows the whole scene; its 5 steps are listed below.
Every step as text
- You type a command. You type dotnet run hello.cs -- Asha Ravi. The shell splits the line into words and starts dotnet with all of them.
- dotnet takes its own words. dotnet run reads its own options and the file name. Everything after -- belongs to your program, even a word such as --help that dotnet would otherwise read as its own option.
- args arrives. The program starts with args holding the rest: args[0] is Asha and args[1] is Ravi. With no arguments, args is an empty array, never null.
- The program returns. The top-level code ends with return 3, as entry.cs does. That int becomes the exit code of the process; ending without a return gives 0.
- dotnet passes it on. dotnet run ends with the exit code of the program it ran, so the shell sees 3 as well: echo $? prints it, and a command after && does not run.
dotnet run hands the exit code of your program back as its own, so a shell can test it: in Bash or zsh, echo $?
prints the exit code of the last command, and dotnet run tool.cs && echo done prints done only after an exit code
of 0. Top-level code that ends without return exits with 0, and so does any program whose statements all run to the
end. An exception that nothing catches ends the program with a non-zero code instead.
Key idea
Words after the file name, or after --, arrive in args; the int your top-level code returns becomes the exit
code that the shell and every build script read, with 0 for success.
Checkpoint
Check: Arguments and exit codes
A shebang line runs a C# file as a script on macOS and Linux
On macOS and Linux a file can name the program that runs it in its first line, a shebang: A first line that starts with #! and names the program that runs the file. On macOS and Linux it lets you run a script by its own name, such as ./count.cs.
that starts with #!. This script counts the non-empty lines it reads from standard input and fails when there are
none:
#!/usr/bin/env -S dotnet --
#:property PublishAot=false
// Counts the non-empty lines it reads from standard input, and fails when there are none.
int count = 0;
while (Console.ReadLine() is string line)
{
if (line.Trim().Length > 0)
{
count++;
}
}
Console.WriteLine($"{count} non-empty lines");
return count > 0 ? 0 : 1; Line 1 runs the file with dotnet, found on PATH by env. The -S lets env pass dotnet and -- as two
words, and the -- makes dotnet hand every argument to the script instead of reading it as an option of its own.
Line 2 switches off a build setting that file-based apps have by default, so the script also runs on a computer
without internet; the next lesson shows what that setting downloads. The session makes the file executable and runs it
twice, once on four lines of marks and once on no input at all:
from session import run
# count.cs as a script: the shebang line runs it with dotnet, once the file may be executed.
run("""
cd script
chmod +x count.cs
./count.cs < marks.txt
!./count.cs < /dev/null
""") Output
$ cd script $ chmod +x count.cs $ ./count.cs < marks.txt 3 non-empty lines $ ./count.cs < /dev/null 0 non-empty lines [exit status 1]
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 script_session.py
The first run counted 3 non-empty lines out of 4, because one line of marks.txt is empty. The second read nothing
from /dev/null, printed 0 and ended with exit status 1, from its last line,
return count > 0 ? 0 : 1;. Two details decide whether a shebang works: save the file with LF line endings, not
Windows’ CRLF, and without a byte-order mark, because the system reads the first bytes of the file as the program to
run. Windows has no shebang; there you run the same file with dotnet run count.cs.
Checkpoint
Check: C# scripts with a shebang
Practise: run, return and script
These questions cover the four ideas of the lesson, and each one comes back with the reason behind its answer:
Practice that adapts to you
Practise this lesson
9 questions about 4 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.
Change the greeting
Copy hello.cs into an empty folder and run dotnet run hello.cs -- --help. Then add return args.Length; as its
last line, run it with three names and print the exit code with echo $? (in PowerShell, $LASTEXITCODE).
Interview questions
Fresher · What are top-level statements in C#, and what does the compiler do with them?
Top-level statements are code written directly in a file, outside any class or method, as in a one-line
Console.WriteLine("Hi"); program. The compiler puts them into a method of a class named Program, which it writes
itself, and makes that the program’s entry point: args holds the command-line arguments, return with an int
sets the exit code, and await is allowed, in which case the method is async. Only one file of a project may hold
top-level statements, because a program has one entry point. using directives must come first in that file, and
types may be declared after the statements. They compile to the same kind of code as a written-out Main method, so
they suit programs of any size.
Model answers open with any pass: ₹79 for 1 week ($2.99), one payment that never renews.
-
Fresher to mid
A C# file-based tool runs in your CI pipeline. It prints "error: 3 files missing", yet the pipeline step passes and a later step fails in a confusing way. Explain why, and how you would make the tool report its failure properly.
-
Mid to senior
Your team keeps its small automation scripts in Bash and Python. Someone proposes C# file-based apps instead. What would you check before agreeing, and when should such a script become a project?
Key takeaways
dotnet run hello.csbuilds a single.csfile and runs it, with no project file; arguments go after the file name, or after--when they look like options ofdotnet.- Behind
dotnet run, the SDK writes a hidden project, restores, builds into a cache folder for that file and reuses the build while nothing changes. - Top-level statements become a method of a class named
Programthat the compiler writes, and implicit usings make namespaces such asSystem.Collections.Genericavailable withoutusinglines. argsbrings the command-line words in andreturnsends an exit code out, whichdotnet runpasses on to the shell; 0 means success.- On macOS and Linux, a first line
#!/usr/bin/env -S dotnet --, LF line endings andchmod +xlet you run a C# file by its name.
Terms in this lesson
- entry point
- The method that the runtime calls when a program starts, usually Main. With top-level statements the compiler writes it for you.
- exit code
- The whole number a program hands back to the shell or the program that started it when it ends. 0 means success; any other value means some kind of failure.
- file-based app
- A C# program in a single .cs file that dotnet run builds and runs without a project file. The SDK generates the project behind it, new in .NET 10.
- implicit usings
- Global using directives that the SDK adds to a project, so its files can use types of namespaces such as System, System.IO and System.Linq without writing using lines.
- shebang
- A first line that starts with #! and names the program that runs the file. On macOS and Linux it lets you run a script by its own name, such as ./count.cs.
- top-level statements
- Code written directly in a file, outside any class or method. The compiler puts it into a method of a class named Program, which becomes the program's entry point.
References
- File-based apps (Microsoft Learn)
- Build file-based apps (tutorial) (Microsoft Learn)
- Top-level statements (Microsoft Learn)
- .NET project SDK overview (implicit using directives) (Microsoft Learn)
- dotnet run (Microsoft Learn)
Related tools
Report a problem with this lesson
Kept only in this browser. Your Learn progress