D1.0 What This Teaches
A .NET solution can contain a class library project and multiple console projects
that reference it. Each console project becomes a runnable demo for the library.
This tutorial covers:
- Organizing a solution with a library project and separate demo projects
- Adding a project reference so demos can use library types
- The
dotnet run --project command for running individual demos
- The library class and each demo's
Program.cs
D1.1 Why Separate Demo Projects?
A class library has no entry point - it cannot run on its own. Separate console
projects serve as runnable demonstrations that call into the library. Keeping each
demo in its own project makes it easy to run, extend, or remove independently.
The pattern mirrors what Cargo's examples/ directory does for Rust and
CMake's demos/ subdirectory does for C++: each demo is a standalone
executable that imports from a shared library.
D1.2 Project Layout
Demonstrations/
├── Demonstrations.sln ← solution file (links all projects)
├── Lib/
│ ├── Lib.csproj ← class library project
│ └── DemoLib.cs ← public types and functions
└── Demos/
├── Basic/
│ ├── Basic.csproj ← console app, references Lib
│ └── Program.cs
├── Words/
│ ├── Words.csproj
│ └── Program.cs
└── Shapes/
├── Shapes.csproj
└── Program.cs
The solution file tracks all projects. A project reference in each demo's
.csproj tells the build system to compile the library and make its
public types available.
D1.3 Creating the Solution Structure
Run these commands from a terminal, starting in an empty Demonstrations/ folder:
dotnet new sln --name Demonstrations
dotnet new classlib --name Lib --output Lib
dotnet new console --name Basic --output Demos/Basic
dotnet new console --name Words --output Demos/Words
dotnet new console --name Shapes --output Demos/Shapes
dotnet sln add Lib/Lib.csproj
dotnet sln add Demos/Basic/Basic.csproj
dotnet sln add Demos/Words/Words.csproj
dotnet sln add Demos/Shapes/Shapes.csproj
dotnet add Demos/Basic/Basic.csproj reference Lib/Lib.csproj
dotnet add Demos/Words/Words.csproj reference Lib/Lib.csproj
dotnet add Demos/Shapes/Shapes.csproj reference Lib/Lib.csproj
After these commands the solution is configured. Delete the generated
Class1.cs placeholder in Lib/ and replace it with
DemoLib.cs.
D1.4 Lib/Lib.csproj
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
</Project>
A class library omits <OutputType>Exe</OutputType>. The
default output type is a .dll that other projects reference.
D1.5 The Library - Lib/DemoLib.cs
// DemoLib.cs - public API for the Demonstrations library.
namespace DemoLib;
public static class MathUtils
{
public static int Add(int a, int b) => a + b;
public static int Clamp(int value, int lo, int hi) =>
value < lo ? lo : value > hi ? hi : value;
public static int WordCount(string s) =>
s.Split(' ', StringSplitOptions.RemoveEmptyEntries).Length;
}
public class Circle(double radius)
{
public double Radius { get; } = radius;
public double Area() => Math.PI * Radius * Radius;
public double Circumference() => 2 * Math.PI * Radius;
public override string ToString() => $"Circle(r={Radius})";
}
public class Rectangle(double width, double height)
{
public double Width { get; } = width;
public double Height { get; } = height;
public double Area() => Width * Height;
public double Perimeter() => 2 * (Width + Height);
public bool IsSquare() => Width == Height;
public override string ToString() => $"Rectangle({Width}x{Height})";
}
Primary constructors (class Circle(double radius)) are a C# 12 feature
that declares constructor parameters as part of the class declaration. The properties
are initialized directly in the property declaration.
D1.6 Demo Project File - Demos/Basic/Basic.csproj
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="../../Lib/Lib.csproj" />
</ItemGroup>
</Project>
<OutputType>Exe</OutputType> makes this a console executable.
<ProjectReference> pulls in the library so using DemoLib;
resolves at compile time. The Words and Shapes project files
follow the same pattern with their own names.
D1.7 Demo: Basic/Program.cs
// Basic/Program.cs - demonstrates Add and Clamp from DemoLib.
// Run with: dotnet run --project Demos/Basic
using DemoLib;
Console.WriteLine("=== basic demo ===");
Console.WriteLine($"Add(7, 3) = {MathUtils.Add(7, 3)}");
Console.WriteLine($"Clamp(25, 0, 20) = {MathUtils.Clamp(25, 0, 20)}");
Console.WriteLine($"Clamp(-5, 0, 20) = {MathUtils.Clamp(-5, 0, 20)}");
Console.WriteLine($"Clamp(10, 0, 20) = {MathUtils.Clamp(10, 0, 20)}");
Top-level statements (no explicit class or Main) are
available in C# 9 and later. The compiler generates the entry point automatically.
ImplicitUsings in the project file makes System available
without an explicit using System;.
D1.8 Demo: Words/Program.cs
// Words/Program.cs - demonstrates WordCount from DemoLib.
// Run with: dotnet run --project Demos/Words
using DemoLib;
string text = "the quick brown fox jumps over the lazy dog the fox";
Console.WriteLine("=== words demo ===");
Console.WriteLine($"text: \"{text}\"");
Console.WriteLine($"WordCount: {MathUtils.WordCount(text)}");
D1.9 Demo: Shapes/Program.cs
// Shapes/Program.cs - demonstrates Circle and Rectangle from DemoLib.
// Run with: dotnet run --project Demos/Shapes
using DemoLib;
Console.WriteLine("=== shapes demo ===");
var c = new Circle(5.0);
Console.WriteLine("Circle r=5:");
Console.WriteLine($" area = {c.Area():F4}");
Console.WriteLine($" circumference = {c.Circumference():F4}");
var r = new Rectangle(4.0, 6.0);
Console.WriteLine("Rectangle 4x6:");
Console.WriteLine($" area = {r.Area():F1}");
Console.WriteLine($" perimeter = {r.Perimeter():F1}");
Console.WriteLine($" IsSquare = {r.IsSquare()}");
var sq = new Rectangle(5.0, 5.0);
Console.WriteLine("Rectangle 5x5:");
Console.WriteLine($" IsSquare = {sq.IsSquare()}");
:F4 and :F1 are format specifiers inside interpolated
strings. They map to the same specifiers used with string.Format:
F for fixed-point, followed by the number of decimal places.
D1.10 Build and Run
Build all projects from the solution root:
dotnet build
Run an individual demo:
dotnet run --project Demos/Basic
dotnet run --project Demos/Words
dotnet run --project Demos/Shapes
| Command | What it does |
dotnet build | Build all projects in the solution |
dotnet run --project Demos/Basic | Build and run the Basic demo |
dotnet build --configuration Release | Release build - optimized binaries |
dotnet sln list | Show all projects registered in the solution |
dotnet add <proj> reference <lib> | Add a project reference from a demo to the library |
D1.11 Expected Outputs
dotnet run --project Demos/Basic
=== basic demo ===
Add(7, 3) = 10
Clamp(25, 0, 20) = 20
Clamp(-5, 0, 20) = 0
Clamp(10, 0, 20) = 10
dotnet run --project Demos/Words
=== words demo ===
text: "the quick brown fox jumps over the lazy dog the fox"
WordCount: 11
dotnet run --project Demos/Shapes
=== shapes demo ===
Circle r=5:
area = 78.5398
circumference = 31.4159
Rectangle 4x6:
area = 24.0
perimeter = 20.0
IsSquare = False
Rectangle 5x5:
IsSquare = True
D1.12 Exercise
Exercise
- Add a
UniqueWords(string s) static method to MathUtils
that returns IEnumerable<string> of distinct words in sorted
order.
- Create a new console project
Demos/Stats/ and add it to the
solution and to the library reference.
- In
Stats/Program.cs, call both WordCount and
UniqueWords on several strings and print the results.
D1.13 Common Mistakes
Forgetting to add the project reference
Creating the demo project and adding it to the solution does not automatically
link it to the library. You must also run
dotnet add <demo.csproj> reference <lib.csproj> or
edit the .csproj file to add a <ProjectReference>
element.
Forgetting to add the project to the solution
A project that exists on disk but is not registered in the solution file is
invisible to dotnet build at the solution level. Run
dotnet sln add <proj.csproj> to register it.
Missing namespace in using statement
using MathUtils; // error: MathUtils is a class, not a namespace
using DemoLib; // correct: DemoLib is the namespace
The namespace declaration in DemoLib.cs is
namespace DemoLib. Use that name in the using
statement, not the class name.
Running dotnet run without --project from the solution root
dotnet run without --project fails at the solution
level because it cannot determine which project to run. Always specify
--project Demos/Basic (or the path to the .csproj file).
D1.14 Key Terms
| Term | Meaning |
| solution (.sln) | File that groups multiple .NET projects for building together |
| class library | Project that compiles to a .dll with no entry point |
| console app | Project with OutputType=Exe; has an entry point and runs standalone |
| ProjectReference | .csproj element that links one project to another at build time |
| dotnet run --project | Build and run a specific project from anywhere in the repo |
| top-level statements | C# 9+ feature: code at the file level becomes the entry point |
| primary constructor | C# 12+ feature: parameters declared on the class header |
| format specifier | Suffix after : in an interpolated string, e.g. :F4 for 4 decimal places |