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

Projects, solutions and the dotnet CLI

Create C# library, console and test projects with the dotnet CLI, read a .csproj, join the projects in an .slnx solution, then build, run and test them.

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

What you will learn

  • Create class library, console and test projects with dotnet new, and reference one project from another
  • Read a .csproj and explain its Sdk, OutputType, TargetFramework, ImplicitUsings and Nullable settings
  • Build, run and test a solution of several projects in the .slnx format from the command line
  • Choose the dotnet command for an everyday task, in its .NET 10 noun-first form where there is one

Before you start

What you’ll learn · Before you start

What you will learn

  • Create class library, console and test projects with dotnet new, and reference one project from another
  • Read a .csproj and explain its Sdk, OutputType, TargetFramework, ImplicitUsings and Nullable settings
  • Build, run and test a solution of several projects in the .slnx format from the command line
  • Choose the dotnet command for an everyday task, in its .NET 10 noun-first form where there is one

Before you start

On this page

A file-based app is one file, but a real program soon has a library, an app that uses it and tests that check it: three projects that must be built in the right order and tested with one command. In .NET each of them is a project file: A .csproj file for C#: MSBuild XML that describes one thing to build, with its SDK, its target framework, its settings, its package references and its references to other projects., and a solution: An .slnx file, or an older .sln file, that lists projects so that one command builds or tests all of them and an IDE opens them together. It compiles nothing itself. lists them: the one this lesson creates for its first two projects is 6 lines long, where the older solution format needs 54 for the same two.

This lesson builds such a solution with the dotnet command: it creates the projects from templates, reads what a project file says and what the SDK fills in, follows one project’s build into another, runs tests that catch a rounding mistake, and maps the everyday tasks to their commands.

Five commands make a solution of two projects

dotnet new creates files from the templates that come with the SDK, and dotnet sln edits a solution file. This session starts in an empty folder and makes a class library for the interest calculation, a console app, and the solution that holds them:

A solution with a library and an app, from templates Python · create_session.py
from session import run

# A solution made from the SDK's templates in an empty folder: a class library, a console app that references it, and
# the solution file that lists both. Then the same two projects in a solution of the older .sln format, to compare
# the two files.
run("""
mkdir interest
cd interest
dotnet new sln -n Interest
dotnet new classlib -n InterestLib -o src/InterestLib
dotnet new console -n InterestApp -o src/InterestApp
dotnet sln add src/InterestLib src/InterestApp
dotnet reference add src/InterestLib --project src/InterestApp
cat Interest.slnx
cat src/InterestLib/InterestLib.csproj
cat src/InterestApp/InterestApp.csproj
dotnet build
dotnet run --project src/InterestApp
dotnet new sln -n Classic --format sln
dotnet sln Classic.sln add src/InterestLib src/InterestApp
wc -l Interest.slnx Classic.sln
""")

Output

$ mkdir interest
$ cd interest
$ dotnet new sln -n Interest
The template "Solution File" was created successfully.
$ dotnet new classlib -n InterestLib -o src/InterestLib
The template "Class Library" was created successfully.

Processing post-creation actions...
Restoring interest/src/InterestLib/InterestLib.csproj:
  Determining projects to restore...
  Restored interest/src/InterestLib/InterestLib.csproj (in …).
Restore succeeded.
$ dotnet new console -n InterestApp -o src/InterestApp
The template "Console App" was created successfully.

Processing post-creation actions...
Restoring interest/src/InterestApp/InterestApp.csproj:
  Determining projects to restore...
  Restored interest/src/InterestApp/InterestApp.csproj (in …).
Restore succeeded.
$ dotnet sln add src/InterestLib src/InterestApp
Project `src/InterestLib/InterestLib.csproj` added to the solution.
Project `src/InterestApp/InterestApp.csproj` added to the solution.
$ dotnet reference add src/InterestLib --project src/InterestApp
Reference `..\InterestLib\InterestLib.csproj` added to the project.
$ cat Interest.slnx
<Solution>
  <Folder Name="/src/">
    <Project Path="src/InterestApp/InterestApp.csproj" />
    <Project Path="src/InterestLib/InterestLib.csproj" />
  </Folder>
</Solution>
$ cat src/InterestLib/InterestLib.csproj
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>net10.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

</Project>
$ cat src/InterestApp/InterestApp.csproj
<Project Sdk="Microsoft.NET.Sdk">

  <ItemGroup>
    <ProjectReference Include="..\InterestLib\InterestLib.csproj" />
  </ItemGroup>

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net10.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

</Project>
$ dotnet build
  Determining projects to restore...
  Restored interest/src/InterestApp/InterestApp.csproj (in …).
  1 of 2 projects are up-to-date for restore.
  InterestLib -> interest/src/InterestLib/bin/Debug/net10.0/InterestLib.dll
  InterestApp -> interest/src/InterestApp/bin/Debug/net10.0/InterestApp.dll

Build succeeded.
    0 Warning(s)
    0 Error(s)

Time Elapsed …
$ dotnet run --project src/InterestApp
Hello, World!
$ dotnet new sln -n Classic --format sln
The template "Solution File" was created successfully.
$ dotnet sln Classic.sln add src/InterestLib src/InterestApp
Project `src/InterestLib/InterestLib.csproj` added to the solution.
Project `src/InterestApp/InterestApp.csproj` added to the solution.
$ wc -l Interest.slnx Classic.sln
       6 Interest.slnx
      54 Classic.sln
      60 total

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 create_session.py

Five commands did the work. dotnet new sln -n Interest wrote Interest.slnx, an XML file: since .NET 10 the template writes the .slnx format, and up to .NET 9 it wrote an .sln file. dotnet new classlib and dotnet new console each wrote a project into its own folder under src and restored it straight away. dotnet sln add listed both projects in the solution, and because their paths start with src, it put them in a solution folder named /src/, as Microsoft’s documentation of dotnet sln describes; --in-root would have left them at the top. dotnet reference add made the app use the library, which the next sections look into.

dotnet build in the folder of the solution then built both projects, the library first, and dotnet run --project src/InterestApp ran the app, which still printed the template’s Hello, World!. The last three commands made the same solution in the older .sln format for comparison: wc -l counted 6 lines for Interest.slnx and 54 for Classic.sln, where each project’s generated id is repeated on 12 lines of build configurations. dotnet sln migrate turns an existing .sln into an .slnx.

Run solution commands where the solution is

dotnet build, dotnet test and dotnet sln look for one solution or project file in the current folder. Run them in the solution’s folder, or name the file, as in dotnet build Interest.slnx.

Checkpoint

Check: Solutions and the .slnx format

  1. Question 1 of 2 Which file did dotnet new sln -n Interest write with the .NET 10 SDK?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: Interest.slnx, an XML solution file

    dotnet new sln makes an .slnx file in .NET 10. dotnet sln migrate turns an existing .sln into one.

  2. Question 2 of 2 dotnet sln add src/InterestLib src/InterestApp put both projects inside <Folder Name="/src/"> in Interest.slnx. Why?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: Their paths start with a folder, src, which dotnet sln add turns into a solution folder

    A solution folder only organises the list of projects. The projects stay where they are on disk.

A project file names an SDK, and the SDK fills in the rest

This is the library’s project file, as dotnet new classlib wrote it. It names no source file at all:

InterestLib.csproj CSPROJ · solution/src/InterestLib/InterestLib.csproj
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>net10.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

</Project>

The Sdk attribute does most of the work. It makes MSBuild, the build engine behind dotnet build, import the SDK’s own settings before and after your file, so your file only states what differs. Each of its three properties is a setting you will meet in every project:

Property Here What it decides
TargetFramework net10.0 which .NET the project is built for, and so its libraries and its default C# version
ImplicitUsings enable adds global using lines for System, System.IO, System.Linq and four more namespaces
Nullable enable turns on the warnings about null, such as CS8602
OutputType not set: Library Exe for a program you can run, as in the console project

Step through what MSBuild makes of the file, then see the values it worked out and what happens when a file is listed by hand:

Interactive · stage by stage From seven lines of XML to a built library Step through what MSBuild reads and works out before it compiles InterestLib.
From seven lines of XML to a built library, step 5 of 5. Showing Sdk.props, Your PropertyGroup (net10.0, enable, enable), Sdk.targets (Library, C# 14.0), Default items (SimpleInterest.cs), Compile. In focus: Compile.Sdk.propsdefaultsYour PropertyGroupnet10.0, enable, enableSdk.targetsLibrary, C# 14.0Default itemsSimpleInterest.csCompileInterestLib.dllcsc

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

Every step as text
  1. The Sdk attribute. Project Sdk="Microsoft.NET.Sdk" makes MSBuild import the SDK's Sdk.props before the first line of your file, with the defaults every .NET project starts from.
  2. Your three properties. Then your PropertyGroup sets TargetFramework to net10.0, and ImplicitUsings and Nullable to enable. Everything you leave out keeps the SDK's value.
  3. The values you never wrote. Sdk.targets is imported after your file and works out the values that depend on yours: LangVersion becomes 14.0, the default C# of net10.0. OutputType keeps the default, Library, because nothing asked for Exe. dotnet msbuild -getProperty printed both.
  4. Every .cs file. No file is listed, yet SimpleInterest.cs is compiled: the SDK includes every .cs file in the project folder and below it as a Compile item, except those under bin and obj.
  5. The build. The compiler turns the Compile items into bin/Debug/net10.0/InterestLib.dll. Listing SimpleInterest.cs again in the project file would include it twice, which is error NETSDK1022.

The values MSBuild worked out, and a file listed twice Python · props_session.py
from session import run

# What MSBuild worked out for the library of the finished solution, from its seven-line project file. Then duplicate,
# a project that also lists a file that the SDK already compiles.
run("""
cd solution
dotnet msbuild src/InterestLib -getProperty:OutputType -getProperty:TargetFramework -getProperty:Nullable -getProperty:LangVersion
cd ../duplicate
!dotnet build -v q
""")

Output

$ cd solution
$ dotnet msbuild src/InterestLib -getProperty:OutputType -getProperty:TargetFramework -getProperty:Nullable -getProperty:LangVersion
{
  "Properties": {
    "OutputType": "Library",
    "TargetFramework": "net10.0",
    "Nullable": "enable",
    "LangVersion": "14.0"
  }
}
$ cd ../duplicate
$ dotnet build -v q
…/dotnet-root/sdk/10.0.401/Sdks/Microsoft.NET.Sdk/targets/Microsoft.NET.Sdk.DefaultItems.Shared.targets(190,5): error NETSDK1022: Duplicate 'Compile' items were included. The .NET SDK includes 'Compile' items from your project directory by default. You can either remove these items from your project file, or set the 'EnableDefaultCompileItems' property to 'false' if you want to explicitly include them in your project file. For more information, see https://aka.ms/sdkimplicititems. The duplicate items were: 'Helper.cs' [duplicate/Duplicate.csproj]

Build FAILED.

…/dotnet-root/sdk/10.0.401/Sdks/Microsoft.NET.Sdk/targets/Microsoft.NET.Sdk.DefaultItems.Shared.targets(190,5): error NETSDK1022: Duplicate 'Compile' items were included. The .NET SDK includes 'Compile' items from your project directory by default. You can either remove these items from your project file, or set the 'EnableDefaultCompileItems' property to 'false' if you want to explicitly include them in your project file. For more information, see https://aka.ms/sdkimplicititems. The duplicate items were: 'Helper.cs' [duplicate/Duplicate.csproj]
    0 Warning(s)
    1 Error(s)

Time Elapsed …
[exit status 1]

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 props_session.py

dotnet msbuild -getProperty printed the evaluated values as JSON: Library for the OutputType nobody set, and 14.0 for LangVersion, the default C# of net10.0. The duplicate project lists Helper.cs in an ItemGroup, and the build stopped with NETSDK1022, because the SDK already compiles every .cs file in the project’s folder and the folders below it, except bin and obj.

The target framework: The version of .NET a project is built for, written as a short name such as net10.0. It decides which library types are available and the project's default C# version. More in “What C# and .NET are, and which version to use” is a short name. net10.0 is the usual choice for apps and libraries today; netstandard2.1 is for a library that other .NET implementations load too, such as Unity, whose manual names .NET Standard 2.1 as its default API level; and net481 is .NET Framework 4.8.1, the latest version of the Windows-only .NET Framework. A library built for several of them uses the plural property, <TargetFrameworks>net10.0;netstandard2.1</TargetFrameworks>, and is compiled once for each.

Checkpoint

Check: What a project file says

  1. Question 1 of 2 InterestLib.csproj sets no OutputType. What did dotnet msbuild -getProperty:OutputType print for it?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: Library

    The SDK imports defaults before and after your file, so dotnet msbuild -getProperty shows values that the project file never states, such as OutputType Library and LangVersion 14.0.

  2. Question 2 of 2 A project file lists <Compile Include="Helper.cs" />, and Helper.cs is in the project's folder. What happens when it builds?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: It fails with NETSDK1022: the SDK already includes every .cs file, so Helper.cs is included twice

    SDK-style projects compile every .cs file below the project folder, except bin and obj, so you list files only when you have switched the defaults off.

A project reference builds the library first and copies it next to the app

dotnet reference add changed only the app’s project file: it added an ItemGroup with one project reference: A ProjectReference item in a project file. It makes the referenced project build first, and its .dll is copied next to the output of the project that uses it., written with backslashes even on macOS, where MSBuild accepts them too:

InterestApp.csproj CSPROJ · solution/src/InterestApp/InterestApp.csproj
<Project Sdk="Microsoft.NET.Sdk">

  <ItemGroup>
    <ProjectReference Include="..\InterestLib\InterestLib.csproj" />
  </ItemGroup>

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net10.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

</Project>

The finished solution of this lesson has the same two projects with real code in them, plus a test project. The library works in whole paise, a long for each amount and a decimal for the rate, refuses negative inputs and rounds the interest to the nearest paisa:

SimpleInterest.cs, in InterestLib C# · solution/src/InterestLib/SimpleInterest.cs
namespace InterestLib;

/// <summary>Simple interest on an amount in paise, rounded to the nearest paisa.</summary>
public static class SimpleInterest
{
    public static long Of(long principalPaise, decimal ratePercent, int months)
    {
        ArgumentOutOfRangeException.ThrowIfNegative(principalPaise);
        ArgumentOutOfRangeException.ThrowIfNegative(ratePercent);
        ArgumentOutOfRangeException.ThrowIfNegative(months);
        decimal interest = principalPaise * ratePercent / 100m * months / 12m;
        return (long)Math.Round(interest, MidpointRounding.AwayFromZero);
    }
}
Program.cs, in InterestApp C# · solution/src/InterestApp/Program.cs
using InterestLib;

long interest = SimpleInterest.Of(principalPaise: 5_000_000, ratePercent: 8m, months: 12);
Console.WriteLine($"Interest on 5000000 paise at 8% for 12 months: {interest} paise");

The app’s using InterestLib; names the library’s namespace, which the project reference makes available. This session builds and runs the app, then looks at what the build left behind:

Building the app builds the library, and copies it Python · output_session.py
from session import run

# The app of the finished solution, built and run: the library is built first, and its .dll lands next to the app's.
# A Release build then goes beside the Debug one.
run("""
cd solution
dotnet build src/InterestApp
dotnet run --project src/InterestApp
ls src/InterestApp/bin/Debug/net10.0
ls src/InterestApp/obj
dotnet build src/InterestApp -c Release -v q
ls src/InterestApp/bin
""")

Output

$ cd solution
$ dotnet build src/InterestApp
  Determining projects to restore...
  Restored solution/src/InterestApp/InterestApp.csproj (in …).
  Restored solution/src/InterestLib/InterestLib.csproj (in …).
  InterestLib -> solution/src/InterestLib/bin/Debug/net10.0/InterestLib.dll
  InterestApp -> solution/src/InterestApp/bin/Debug/net10.0/InterestApp.dll

Build succeeded.
    0 Warning(s)
    0 Error(s)

Time Elapsed …
$ dotnet run --project src/InterestApp
Interest on 5000000 paise at 8% for 12 months: 400000 paise
$ ls src/InterestApp/bin/Debug/net10.0
InterestApp
InterestApp.deps.json
InterestApp.dll
InterestApp.pdb
InterestApp.runtimeconfig.json
InterestLib.dll
InterestLib.pdb
$ ls src/InterestApp/obj
Debug
InterestApp.csproj.nuget.dgspec.json
InterestApp.csproj.nuget.g.props
InterestApp.csproj.nuget.g.targets
project.assets.json
project.nuget.cache
$ dotnet build src/InterestApp -c Release -v q

Build succeeded.
    0 Warning(s)
    0 Error(s)

Time Elapsed …
$ ls src/InterestApp/bin
Debug
Release

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 output_session.py

Building src/InterestApp built InterestLib first, because the app cannot be compiled without it, and the app printed 400000 paise of interest on 5000000 paise at 8 % for 12 months. bin/Debug/net10.0 holds the app and, copied next to it, InterestLib.dll, so the app can run from that folder alone. obj holds the build’s working files, such as project.assets.json, the result of the restore. -c Release built a second, optimized copy into bin/Release; Microsoft’s compiler reference says optimization is on by default for Release builds only.

Adding bin and obj to source control

Both folders are rebuilt from the source on every computer, and their files change with every build. Keep them out of the repository: dotnet new gitignore writes a .gitignore file that already lists them.

Step through a build of all three projects of the solution:

Interactive · step by step One dotnet build of the three projects Step through the build. Watch which project must wait for which, and where each .dll ends up.
One dotnet build of the three projects, step 6 of 6. Showing Interest.slnx, InterestLib (InterestLib.dll), InterestApp (with InterestLib.dll), InterestTests (with InterestLib.dll).referencesreferencesInterest.slnxInterestLibInterestLib.dllInterestAppwith InterestLib.dllInterestTestswith InterestLib.dll

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

Every step as text
  1. The solution names three. dotnet build in the folder of Interest.slnx reads the solution: three projects, two under src and one under tests. The solution lists them; it compiles nothing itself.
  2. Who depends on whom. InterestApp and InterestTests each have a ProjectReference to InterestLib, so InterestLib must be built before either of them can be compiled.
  3. The library first. InterestLib builds first, into src/InterestLib/bin/Debug/net10.0/InterestLib.dll. Nothing waits on anything else for it.
  4. The app, with a copy. InterestApp compiles against that .dll, and the build copies InterestLib.dll into the app's own bin folder, next to InterestApp.dll, so that the app can run on its own.
  5. The tests, with a copy. InterestTests compiles the same way and gets its own copy of InterestLib.dll. Its restore also brought in its one package, xunit.v3.
  6. Built. All three succeeded. A later build compiles only what changed: change only the app, and InterestLib is left as it is.

Key idea

A project reference makes the referenced project build first and its .dll travel with the project that uses it, so code is shared by referencing projects, never by copying files.

Checkpoint

Check: Project references and build output

  1. Question 1 of 2 After dotnet build src/InterestApp, which files did src/InterestApp/bin/Debug/net10.0 hold besides the app's own?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: InterestLib.dll and InterestLib.pdb, copied from the referenced library

    The build of a project with a project reference builds the referenced project first and copies its .dll and .pdb into its own output folder.

  2. Question 2 of 2 You build the app twice, once with dotnet build and once with dotnet build -c Release. Where does each build go?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: To bin/Debug and bin/Release, side by side

    Debug is the default configuration of dotnet build, and -c Release makes an optimized build into its own folder.

dotnet test builds the tests and runs them

A test project is a project too. This one uses xUnit.net v3, one package, and is an Exe, because in this test framework every test project is a program that runs its own tests:

InterestTests.csproj CSPROJ · solution/tests/InterestTests/InterestTests.csproj
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>net10.0</TargetFramework>
    <OutputType>Exe</OutputType>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="xunit.v3" Version="4.0.1" />
  </ItemGroup>

  <ItemGroup>
    <ProjectReference Include="..\..\src\InterestLib\InterestLib.csproj" />
  </ItemGroup>

  <ItemGroup>
    <Using Include="Xunit" />
  </ItemGroup>

</Project>
SimpleInterestTests.cs C# · solution/tests/InterestTests/SimpleInterestTests.cs
using InterestLib;

namespace InterestTests;

public class SimpleInterestTests
{
    [Fact]
    public void A_year_at_8_percent() =>
        Assert.Equal(400_000, SimpleInterest.Of(5_000_000, 8m, 12));

    [Fact]
    public void Half_a_paisa_rounds_up() =>
        Assert.Equal(63, SimpleInterest.Of(10_000, 7.5m, 1));

    [Fact]
    public void Rejects_a_negative_principal() =>
        Assert.Throws<ArgumentOutOfRangeException>(() => SimpleInterest.Of(-1, 8m, 12));
}

The solution’s global.json pins the SDK, as earlier lessons showed, and its test section chooses Microsoft.Testing.Platform, the mode of dotnet test that the .NET 10 SDK added and that xUnit.net’s documentation asks for with SDK 10:

global.json JSON · solution/global.json
{
  "sdk": {
    "version": "10.0.401",
    "rollForward": "latestPatch"
  },
  "test": {
    "runner": "Microsoft.Testing.Platform"
  }
}

In this mode dotnet test still finds the solution in the current folder by itself. To name the file, write dotnet test --solution Interest.slnx: Microsoft’s guide to this mode replaces the bare file name, which dotnet build Interest.slnx still takes, with that option. The session restores the test package from nuget.org, runs the tests, then swaps in a copy of the library with one mistake in it and runs them again:

Three tests pass, then a rounding mistake fails one Python · test_session.py
from session import run

# network: true. The solution's global.json chooses Microsoft.Testing.Platform for dotnet test, so dotnet test
# restores the test project's package, xunit.v3 4.0.1, from nuget.org into the session's empty package folder, builds
# all three projects and runs the tests. Then bug/SimpleInterest.cs, with a rounding mistake, replaces the library's
# file, and the tests run again.
run("""
cd solution
dotnet test
cp ../bug/SimpleInterest.cs src/InterestLib/SimpleInterest.cs
!dotnet test
""", network=True)

Output

$ cd solution
$ dotnet test
Running tests from solution/tests/InterestTests/bin/Debug/net10.0/InterestTests.dll (net10.0|arm64)
solution/tests/InterestTests/bin/Debug/net10.0/InterestTests.dll (net10.0|arm64) passed (…)

Test run summary: Passed!
  total: 3
  failed: 0
  succeeded: 3
  skipped: 0
  duration: …
$ cp ../bug/SimpleInterest.cs src/InterestLib/SimpleInterest.cs
$ dotnet test
Running tests from solution/tests/InterestTests/bin/Debug/net10.0/InterestTests.dll (net10.0|arm64)
failed InterestTests.SimpleInterestTests.Half_a_paisa_rounds_up (…)
  from solution/tests/InterestTests/bin/Debug/net10.0/InterestTests.dll (net10.0|arm64)
  Assert.Equal() Failure: Values differ
  Expected: 63
  Actual:   62
    at InterestTests.SimpleInterestTests.Half_a_paisa_rounds_up() in solution/tests/InterestTests/SimpleInterestTests.cs:13
    at System.Reflection.MethodBaseInvoker.InterpretedInvoke_Method(Object obj, IntPtr* args)
    at System.Reflection.MethodBaseInvoker.InvokeWithNoArgs(Object obj, BindingFlags invokeAttr)
solution/tests/InterestTests/bin/Debug/net10.0/InterestTests.dll (net10.0|arm64) failed with 1 error(s) (…)
Exit code: 2

Test run summary: Failed!
  total: 3
  failed: 1
  succeeded: 2
  skipped: 0
  duration: …
Test run completed with non-success exit code: 2 (see: https://aka.ms/testingplatform/exitcodes)
[exit status 2]

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 test_session.py

The first run reported total 3, failed 0, succeeded 3. The copy in bug/ calls Math.Round(interest) without a rounding mode, and Microsoft’s reference says that this overload rounds midpoint values to the nearest even number. The interest on 10,000 paise at 7.5 % for one month is 62.5 paise, so it became 62 instead of 63, and Half_a_paisa_rounds_up failed with Expected: 63 and Actual: 62 at line 13 of the test file. dotnet test then ended with exit status 2, which Microsoft.Testing.Platform uses when at least one test failed, so a build server stops at that step. Step through what dotnet test did:

Interactive · stage by stage What dotnet test does with the solution Step through the first run, where the three tests pass, then the second, after the rounding mistake.
What dotnet test does with the solution, step 6 of 6. Showing Read global.json, Build the solution, Find the test apps (1 test project), Run the tests (1 failed, 2 passed), Summary and status (exit status 2). In focus: Summary and status.Read global.jsontest runner: MTPBuild the solutionrestore, then compileFind the test apps1 test projectRun the tests1 failed, 2 passedSummary and statusexit status 21 failure

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

Every step as text
  1. Which test runner. dotnet test reads the solution's global.json first. Its test section names Microsoft.Testing.Platform, the mode the .NET 10 SDK added for test projects such as xunit.v3 ones.
  2. Build everything. It restores and builds the solution first, as dotnet build would. In the session that meant fetching xunit.v3 4.0.1 from nuget.org, because its package folder started empty.
  3. Find the tests. Of the three projects, only InterestTests is a test application, so dotnet test runs only it. A library or a console app is built but not run.
  4. Run them. The test application runs every method marked [Fact]. All three passed, and the summary said total 3, failed 0, succeeded 3, with exit status 0.
  5. After the rounding mistake. With Math.Round(interest) in the library, Half_a_paisa_rounds_up failed: Expected 63, Actual 62, at line 13 of SimpleInterestTests.cs. The other two still passed.
  6. A failing status. The summary said Failed! and dotnet test ended with exit status 2, the status Microsoft.Testing.Platform uses when at least one test failed, so a build server stops there.

Simple Interest Calculator Work out simple interest for other amounts, then add them to the tests as new cases.

Checkpoint

Check: Test projects and dotnet test

  1. Question 1 of 2 With which exit status did dotnet test end when one of the three tests failed?

    Type a number.

    Show the answer: Question 1 of 2

    Answer: 2

    Microsoft.Testing.Platform uses exit status 2 when at least one test failed, and 0 when every test that ran passed, as in the first run.

  2. Question 2 of 2 Why did Math.Round(62.5m) give 62, which made the test Half_a_paisa_rounds_up fail?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: Without a rounding mode, Math.Round rounds a midpoint to the nearest even number

    The default of Math.Round is banker's rounding, MidpointRounding.ToEven. Pass MidpointRounding.AwayFromZero when halves must always round up, as the correct library does.

Each everyday task has its own dotnet command

Every command so far follows one pattern: dotnet, a command, then its arguments and options. These are the ones a project needs from day to day:

To Type
create a project, a solution or a config file from a template dotnet new console, dotnet new sln, dotnet new gitignore
add a project to a solution, list or remove one dotnet sln add, dotnet sln list, dotnet sln remove
reference another project dotnet reference add ../Lib --project App
add a NuGet package dotnet package add Name --version 1.2.3
fetch the packages without building dotnet restore
compile dotnet build, dotnet build -c Release
build and run one project dotnet run --project src/App
build and run the tests dotnet test
make a folder ready to deploy dotnet publish, a Release build by default
make a NuGet package dotnet pack
fix the formatting of the code dotnet format
delete the build output dotnet clean

.NET 10 added noun-first names for the package and reference commands: dotnet package add and dotnet reference add are the new forms of dotnet add package and dotnet add reference, with list and remove beside add. Microsoft’s notes on the .NET 10 SDK prefer them in scripts and documentation, although the old forms still work.

The commands that build share their main options. -c chooses the configuration, Debug unless you say otherwise, except for dotnet publish and dotnet pack, which build Release by default; -v sets how much they print, from q for quiet through m, n and d to diag; and --no-restore skips the restore when an earlier step of a pipeline has done it. Every command prints all of its options with --help.

Checkpoint

Check: Choosing a dotnet command

  1. Question 1 of 2 Which command is the .NET 10 noun-first form of dotnet add package?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: dotnet package add

    dotnet package add, list and remove, and dotnet reference add, list and remove, are the noun-first forms of the .NET 10 SDK.

  2. Question 2 of 2 You want the folder of files to copy to a server for the app. Which command makes it?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: dotnet publish, which builds Release by default

    dotnet publish is the command for deployment; since .NET 8 it uses the Release configuration by default for projects that target net8.0 or later.

Practise: build the solution yourself

Make the whole solution in an empty folder, without copying the finished one. Create the solution and the two projects with dotnet new, add a third project for the tests with dotnet new classlib -o tests/InterestTests, delete its Class1.cs and replace its project file with the one above, then connect everything with dotnet sln add and dotnet reference add. Add the global.json, copy in SimpleInterest.cs and the tests, and run dotnet test until it reports 3 passed. Then add a test of your own that a negative rate is refused, and check that it passes too.

The questions below cover the five ideas of this lesson. Those you have not yet got right come first, and every choice carries its reason:

Practice that adapts to you

Practise this lesson

11 questions about 5 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.

  1. Question 1 of 11 What does a solution file contain?

    Choose one answer.

    Show the answer: Question 1 of 11

    Answer: A list of projects, so that one command builds and tests them all

    A solution is a list of projects. A project can be in several solutions, and a project left out of a solution is not built by commands run on it.

  2. Question 2 of 11 A teammate creates tests/More.Tests with dotnet new and pushes it. CI runs dotnet test on the solution and reports success. Were the new tests run?

    Choose one answer.

    Show the answer: Question 2 of 11

    Answer: Only if someone ran dotnet sln add for it: a project missing from the solution is not built or tested

    After dotnet new, run dotnet sln add for the new project and check with dotnet sln list, or the solution never sees it.

  3. Question 3 of 11 What does Sdk="Microsoft.NET.Sdk" on the Project element do?

    Choose one answer.

    Show the answer: Question 3 of 11

    Answer: It makes MSBuild import the SDK's settings before and after the project file

    The project SDK supplies Sdk.props and Sdk.targets, with the defaults, the default items and the build steps.

  4. Question 4 of 11 A library must be usable from a .NET 10 app and from a Unity game. Which project setting fits?

    Choose one answer.

    Show the answer: Question 4 of 11

    Answer: <TargetFrameworks>net10.0;netstandard2.1</TargetFrameworks>

    Multi-targeting compiles the same code for each framework in the list, so each caller gets a build it can load.

  5. Question 5 of 11 What does the obj folder of a project hold?

    Choose one answer.

    Show the answer: Question 5 of 11

    Answer: The build's working files, such as project.assets.json from the restore

    bin holds the outputs and obj the intermediate files. Both are rebuilt from the sources, which is why they stay out of source control.

  6. Question 6 of 11 Two apps need the same calculation code. What is the right way to share it?

    Choose one answer.

    Show the answer: Question 6 of 11

    Answer: Put it in a class library and add a project reference to it from both apps

    A project reference keeps one copy of the code, builds it in the right order and carries its output along.

  7. Question 7 of 11 Why is the xUnit.net v3 test project an Exe?

    Choose one answer.

    Show the answer: Question 7 of 11

    Answer: An xUnit.net v3 test project is a program that runs its own tests

    With xUnit.net v3 on Microsoft.Testing.Platform, test projects are standalone executables, and dotnet test runs them for you.

  8. Question 8 of 11 Which file told dotnet test to use Microsoft.Testing.Platform?

    Choose one answer.

    Show the answer: Question 8 of 11

    Answer: The solution's global.json, with "test": { "runner": "Microsoft.Testing.Platform" }

    global.json pins the SDK and, from .NET 10, chooses the test runner for every project below it.

  9. Question 9 of 11 A CI step runs dotnet test. One test fails. What makes the CI job stop at that step?

    Choose one answer.

    Show the answer: Question 9 of 11

    Answer: The exit status 2, which marks the step as failed

    A test runner reports failure through its exit status, which is what makes red builds red.

  10. Question 10 of 11 When do you need to type dotnet restore yourself?

    Choose one answer.

    Show the answer: Question 10 of 11

    Answer: Rarely: build, run, test, publish and pack restore as needed, so it is for separate steps such as a CI stage

    Implicit restore runs in every command that needs it; dotnet restore on its own lets a pipeline restore once and build later with --no-restore.

  11. Question 11 of 11 You are in the solution's folder and want to start the console app. Which command does that?

    Choose one answer.

    Show the answer: Question 11 of 11

    Answer: dotnet run --project src/InterestApp

    dotnet run builds and starts one project, the one in the current folder or the one --project names.

Interview questions

Fresher · What is the difference between a project and a solution in .NET?

A project, a .csproj file for C#, describes one thing to build: a library, an app or a test project, with its target framework, its settings, its package references and its references to other projects; every .cs file in its folder is compiled into one assembly. A solution, an .slnx file since .NET 10 or an older .sln file, only lists projects, so that one command such as dotnet build or dotnet test builds and tests them all and an IDE opens them together. A solution compiles nothing itself, and a project can belong to several solutions. A project that is not listed in the solution is not built or tested by commands run on that solution.

Model answers open with any pass: ₹79 for 1 week ($2.99), one payment that never renews.

  1. Fresher to mid

    A new test project was merged a month ago, and its tests caught a bug only when someone ran them by hand. CI runs dotnet test on the solution and stayed green the whole time. What went wrong, and how do you make sure it cannot happen again?

    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's validation library targets net10.0 and is used by two web services. A game team now wants to call it from Unity. What do you change in the project, and what can go wrong?

    Model answer, Pro. First step and follow-ups

Key takeaways

  • dotnet new makes projects and solutions from templates, and dotnet new sln writes an .slnx file since .NET 10, 6 lines here against 54 for the same projects as an .sln.
  • A project file names an SDK, which imports the defaults, compiles every .cs file below the project and works out values such as OutputType and LangVersion that the file never states.
  • A project reference builds the referenced project first and copies its .dll next to the project that uses it, into bin, while obj keeps the build’s working files.
  • dotnet test, in the mode a global.json chooses, builds the solution, runs every test project and ends with exit status 2 when a test fails, as the rounding mistake showed.
  • Everyday work maps to a handful of commands, and .NET 10’s noun-first forms, such as dotnet reference add, are the ones to use in scripts.

Terms in this lesson

project file
A .csproj file for C#: MSBuild XML that describes one thing to build, with its SDK, its target framework, its settings, its package references and its references to other projects.
project reference
A ProjectReference item in a project file. It makes the referenced project build first, and its .dll is copied next to the output of the project that uses it.
solution
An .slnx file, or an older .sln file, that lists projects so that one command builds or tests all of them and an IDE opens them together. It compiles nothing itself.

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.