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.
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:
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
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:
<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:
The drawing shows the whole scene; its 5 steps are listed below.
Every step as text
- 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.
- 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.
- 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.
- 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.
- 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.
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
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:
<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:
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);
}
} 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:
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:
The drawing shows the whole scene; its 6 steps are listed below.
Every step as text
- 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.
- 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.
- The library first. InterestLib builds first, into src/InterestLib/bin/Debug/net10.0/InterestLib.dll. Nothing waits on anything else for it.
- 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.
- 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.
- 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
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:
<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> 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:
{
"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:
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:
The drawing shows the whole scene; its 6 steps are listed below.
Every step as text
- 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.
- 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.
- 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.
- 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.
- 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.
- 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.
Checkpoint
Check: Test projects and dotnet test
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
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.
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.
-
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?
-
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?
Key takeaways
dotnet newmakes projects and solutions from templates, anddotnet new slnwrites an.slnxfile 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
.csfile below the project and works out values such asOutputTypeandLangVersionthat the file never states. - A project reference builds the referenced project first and copies its
.dllnext to the project that uses it, intobin, whileobjkeeps the build’s working files. dotnet test, in the mode aglobal.jsonchooses, 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
- dotnet new (Microsoft Learn)
- dotnet sln (Microsoft Learn)
- dotnet reference add (Microsoft Learn)
- .NET project SDK overview (Microsoft Learn)
- Target frameworks in SDK-style projects (Microsoft Learn)
- What's new in the SDK and tooling for .NET 10 (Microsoft Learn)
- Testing with 'dotnet test' (Microsoft Learn)
- Microsoft.Testing.Platform troubleshooting (exit codes) (Microsoft Learn)
- xUnit.net v3: Microsoft Testing Platform support (xUnit.net)
- Math.Round method (Microsoft Learn)
- .NET CLI overview (Microsoft Learn)
- Compiler options: code generation (Optimize) (Microsoft Learn)
- dotnet publish (Microsoft Learn)
- Incremental builds in MSBuild (Microsoft Learn)
- .NET profile support (Unity manual) (Unity Technologies)
Related tools
Report a problem with this lesson
Kept only in this browser. Your Learn progress