Site

Demonstrations — Building Demo Projects with dotnet

Tutorial D1.0  •  C# / Learn / Demonstrations

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:

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
CommandWhat it does
dotnet buildBuild all projects in the solution
dotnet run --project Demos/BasicBuild and run the Basic demo
dotnet build --configuration ReleaseRelease build - optimized binaries
dotnet sln listShow 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

TermMeaning
solution (.sln)File that groups multiple .NET projects for building together
class libraryProject that compiles to a .dll with no entry point
console appProject with OutputType=Exe; has an entry point and runs standalone
ProjectReference.csproj element that links one project to another at build time
dotnet run --projectBuild and run a specific project from anywhere in the repo
top-level statementsC# 9+ feature: code at the file level becomes the entry point
primary constructorC# 12+ feature: parameters declared on the class header
format specifierSuffix after : in an interpolated string, e.g. :F4 for 4 decimal places