The Project File

What a .csproj is, why it is XML, and what TargetFramework, Nullable and ImplicitUsings each change about your build.

Syntax<TargetFramework>net10.0</TargetFramework>

Every project has one project file sitting next to the code, named after the project and ending in .csproj. It is the instruction sheet for the build: which .NET version to compile for, which outside libraries to use, which compiler options to switch on.

It is XML because the build engine, MSBuild, reads XML. XML is a text format that nests tagged elements, so <TargetFramework>net10.0</TargetFramework> is one setting named TargetFramework with the value net10.0. The file is short because the opening <Project Sdk="Microsoft.NET.Sdk"> brings in a pile of defaults. One of them compiles every .cs file in the folder and below, so files are never listed by hand.

The settings in a new console project

OutputType set to Exe means the build produces something runnable; leave it out and you get a library, which has no entry point and cannot be started. TargetFramework set to net10.0 means compile against .NET 10's libraries and require that runtime. Nullable set to enable turns on the compiler's null checking, which warns you when a value that might be absent is used as if it never could be. ImplicitUsings set to enable imports seven namespaces into every file for you: System, System.Collections.Generic, System.IO, System.Linq, System.Net.Http, System.Threading and System.Threading.Tasks.

Why net10.0 appears only here

The target framework is a build setting, so no .cs file ever names it. It is also not the SDK version: dotnet --version reports the SDK, a different number, and a newer SDK still builds a net10.0 project unchanged.

Example

Example · csharp
// ImplicitUsings is enabled in the project file, so System.IO is already
// imported here. Without it, this file would need: using System.IO;
Console.WriteLine(Path.GetFileName("reports/january.csv"));

// Nullable is enabled, so the question mark is how you say out loud that
// this variable is allowed to hold nothing at all.
string? missing = null;
Console.WriteLine(missing is null);

// Output:
// january.csv
// True

When to use it

  • Raising an existing project from net8.0 to net10.0 and knowing which line to edit.
  • Turning on null checking in a project that predates it.
  • Explaining why a file compiles without a using directive you expected to need.
  • Switching a project from an application to a library.

More examples

The whole project file of a console app

Four settings and nothing else; this is what a .NET 10 console project looks like today. Delete the OutputType line and the same file describes a class library: compiled code meant to be referenced by other projects rather than started.

Example · xml
<Project Sdk="Microsoft.NET.Sdk">

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

</Project>

The long form of ImplicitUsings

Worth reading once so that a file full of using directives does not look like a different language. Turning the setting off is reasonable in a library where you want every dependency visible at the top of the file.

Example · csharp
// With <ImplicitUsings>disable</ImplicitUsings> in the project file, the
// imports have to be written out. These using directives are exactly what
// the setting was adding for you, so the two versions are the same program.
using System;
using System.IO;

Console.WriteLine(Path.GetFileName("reports/january.csv"));

// Output:
// january.csv

Adjusting the defaults

Use Using items when a namespace appears in nearly every file, and Remove when an implicit import is getting in your way. TreatWarningsAsErrors is a cheap way to stop a codebase from accumulating ignored warnings, and it is easiest to adopt on day one.

Example · xml
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net10.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
    <!-- Refuse to build while any warning remains. -->
    <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
  </PropertyGroup>

  <ItemGroup>
    <!-- Import one more namespace in every file of this project. -->
    <Using Include="System.Text.Json" />
    <!-- Stop importing one the SDK adds by default. -->
    <Using Remove="System.Net.Http" />
  </ItemGroup>

</Project>

Discussion

  • Be the first to comment on this lesson.