Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

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.

  • Beginner
  • 10 minutes
  • Examples run with .NET 10.0.401 and Python 3.14.8
  • By MySmartCoPilot
Free lesson, Pro answers: 10 minutes
Free: the whole lesson. With a pass: the model answers to its 2 interview questions.

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:

hello.cs, run with two arguments C# · hello/hello.cs
// 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:

Interactive · stage by stage What dotnet run hello.cs does Step through the first run of hello.cs, then a second run of the same file. The arrow aside is the shortcut the second run takes.
What dotnet run hello.cs does, step 6 of 6. Showing Read hello.cs, Write a hidden project, Restore, Build into a cache, Run the program. In focus: Read hello.cs, Run the program.nothing changedRead hello.csand its #: linesWrite a hidden projectnet10.0, implicit usingsRestorepackages it needsBuild into a cacheone folder per fileRun the programwith its argumentscached build

The drawing shows the whole scene; its 6 steps are listed below.

Every step as text
  1. Read the file. dotnet run hello.cs reads the file, including any #: lines at its top that configure the build. hello.cs has none.
  2. 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.
  3. 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.
  4. 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.
  5. 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.
  6. 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

  1. Question 1 of 2 hello.cs prints a greeting, a line with the number of arguments, then one line per argument. How many lines does dotnet run hello.cs -- Asha Ravi Meera print?

    Type a number.

    A hint

    Count the two fixed lines, then one line for each name.

    Show the answer: Question 1 of 2

    Answer: 5 lines

    The greeting and the line "You passed 3 arguments." come first, then args[0], args[1] and args[2]: 2 + 3 = 5 lines. With Asha and Ravi only, the recorded run printed 4.

  2. Question 2 of 2 You want hello.cs to receive the word --help as its argument. Which command does that?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: dotnet run hello.cs -- --help

    The file name and dotnet's own options come first, then --, then the program's arguments. The -- matters only for arguments that look like options of dotnet, but it never hurts.

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:

entry.cs: what the compiler wrote around top-level statements C# · entry/entry.cs
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

  1. Question 1 of 2 Where does the compiler put the top-level statements of hello.cs?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: In a method of a class named Program, which the compiler writes

    Top-level statements are compiled into a method of a generated class named Program. The method's name, <Main>$, cannot be written in C#, so nothing else can call it.

  2. Question 2 of 2 Which line compiles in a file-based app that has no using directives at all?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: var names = new List<string>();

    The SDK adds global usings for System, System.Collections.Generic, System.IO, System.Linq, System.Net.Http, System.Threading and System.Threading.Tasks. Anything else needs its own using line.

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:

Interactive · message by message Arguments in, an exit code out Step through one run. Watch what dotnet keeps for itself, what reaches the program, and what comes back to the shell.
Arguments in, an exit code out, step 5 of 5. Messages so far: Your shell to dotnet run: hello.cs -- Asha Ravi; dotnet run to Your program: args: Asha, Ravi; Your program to dotnet run: exit code 3; dotnet run to Your shell: exit code 3.Your shellzsh, bash or PowerShelldotnet runkeeps run, hello.csYour programreturn 3hello.cs -- Asha Raviargs: Asha, Raviexit code 3exit code 3

The drawing shows the whole scene; its 5 steps are listed below.

Every step as text
  1. 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.
  2. 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.
  3. 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.
  4. 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.
  5. 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

  1. One question A file-based app ends with return 2. In Bash, what does dotnet run tool.cs && echo done print after the program's own output?

    Choose one answer.

    Show the answer: One question

    Answer: Nothing more: && runs echo only after an exit code of 0

    The exit code of the program becomes the exit code of dotnet run. Shells treat 0 as success, so && continues only then, and echo $? prints the code itself.

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:

script/count.cs C# · script/count.cs
#!/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:

count.cs run as a script Python · script_session.py
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.

Chmod Calculator See which permission bits chmod +x sets on a script, as owner, group and others.

Checkpoint

Check: C# scripts with a shebang

  1. Question 1 of 2 count.cs was written on Windows and copied to Linux. chmod +x was run, and dotnet run count.cs works, but ./count.cs does not start. What is the most likely cause?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: The file has CRLF line endings or a byte-order mark

    Microsoft's guidance is LF line endings and no byte-order mark for files with a shebang. Editors on Windows often save CRLF; convert the file to LF and it runs.

  2. Question 2 of 2 Why does count.cs start with #!/usr/bin/env -S dotnet -- rather than #!/usr/bin/env dotnet?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: -S lets env pass dotnet and -- as two words, and -- stops dotnet from reading the script's arguments as its own options

    A shebang line passes the rest of the line as one word unless env splits it with -S. The -- then forwards every argument to the script. Where -S is missing, #!/usr/bin/env dotnet still works, but dotnet may take arguments that match its own options.

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.

  1. Question 1 of 9 Which command also builds and runs hello.cs as a file-based app?

    Choose one answer.

    Show the answer: Question 1 of 9

    Answer: dotnet hello.cs

    dotnet run hello.cs, dotnet run --file hello.cs and dotnet hello.cs all build and run the file; dotnet build hello.cs only builds it.

  2. Question 2 of 9 You run hello.cs a second time without changing it. What does the SDK do?

    Choose one answer.

    Show the answer: Question 2 of 9

    Answer: It finds its earlier build still valid and runs it without building

    The first run builds into a cache folder for the file; later runs reuse it while nothing it depends on has changed. dotnet clean file-based-apps removes old cached builds.

  3. Question 3 of 9 In a folder that also holds app.csproj, dotnet run hello.cs prints the output of the app.csproj program. Why, and what fixes it?

    Choose one answer.

    Show the answer: Question 3 of 9

    Answer: dotnet run runs the project there and passes hello.cs to it as an argument; use dotnet run --file hello.cs

    With a project file in the current folder, dotnet run without --file runs that project and treats hello.cs as an argument. Microsoft also advises keeping file-based apps out of a project's folders, whose build files would affect them.

  4. Question 4 of 9 What happens when two files of one program both hold top-level statements?

    Choose one answer.

    Show the answer: Question 4 of 9

    Answer: The build fails: only one file may hold top-level statements

    Top-level statements generate the entry point, and a program has exactly one. Other files may declare types, methods in classes and namespaces, but no statements.

  5. Question 5 of 9 Top-level code uses await and ends with return 0. What does the generated method that holds it return?

    Choose one answer.

    Show the answer: Question 5 of 9

    Answer: Task<int>

    The table in the lesson follows from two questions: does the code await, and does it return a value? Both yes gives Task<int>, which is what entry.cs uses; its entry point then waits for the task and returns the int.

  6. Question 6 of 9 Top-level code has no return statement and runs to its end without an error. What is its exit code?

    Type a number.

    Show the answer: Question 6 of 9

    Answer: 0

    A program that ends normally without returning a value exits with 0, the code for success. hello.cs is such a program.

  7. Question 7 of 9 A CI step runs dotnet run check.cs. check.cs prints FAILED: 2 problems, but the step is marked as passed. What is the most likely cause?

    Choose one answer.

    Show the answer: Question 7 of 9

    Answer: The program prints the failure but ends with exit code 0

    Build servers and shells decide success by the exit code. Return a non-zero value, for example return problems > 0 ? 1 : 0;, and the step fails as it should.

  8. Question 8 of 9 Which command makes count.cs runnable as ./count.cs on macOS or Linux, once it has a shebang line?

    Choose one answer.

    Show the answer: Question 8 of 9

    Answer: chmod +x count.cs

    A script needs two things: a shebang line naming the program that runs it, and execute permission, which chmod +x gives.

  9. Question 9 of 9 How do you run count.cs on Windows?

    Choose one answer.

    Show the answer: Question 9 of 9

    Answer: dotnet run count.cs

    The same file runs everywhere with dotnet run count.cs. On macOS and Linux, the shebang line and chmod +x add the shortcut ./count.cs.

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.

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

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page. First step and follow-ups

  2. 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?

    Model answer, Pro. First step and follow-ups

Key takeaways

  • dotnet run hello.cs builds a single .cs file and runs it, with no project file; arguments go after the file name, or after -- when they look like options of dotnet.
  • 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 Program that the compiler writes, and implicit usings make namespaces such as System.Collections.Generic available without using lines.
  • args brings the command-line words in and return sends an exit code out, which dotnet run passes on to the shell; 0 means success.
  • On macOS and Linux, a first line #!/usr/bin/env -S dotnet --, LF line endings and chmod +x let 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

Related tools

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.