100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
.NET

Class Libraries and NuGet Packages

Understand how to build reusable .NET class libraries, reference them across projects, and package and publish them as NuGet packages.

Application TypesIntermediate9 min readJul 10, 2026
Analogies

What Is a Class Library

A class library is a .NET project type (dotnet new classlib) that compiles to a DLL rather than an executable — it has no entry point and cannot be run directly, only referenced. Class libraries target a Target Framework Moniker (TFM) such as net8.0, or netstandard2.0 for broader compatibility with .NET Framework and older runtimes, and they hold shared code — domain models, business logic, data access — that multiple applications or services can consume without duplicating it.

🏏

Cricket analogy: A class library is like a franchise's central bowling coaching manual shared across every age-group team — it isn't itself a match, but every team (application) that references it plays using the same underlying techniques it defines.

Creating and Referencing Class Libraries

Within a solution, one project references another with a ProjectReference in the .csproj file (or dotnet add reference), which compiles the referenced library from source and keeps both projects in lockstep during local development — ideal when you own both the library and its consumer. A PackageReference, by contrast, pulls in a compiled NuGet package by name and version, which is the right choice once a library is versioned, published, and consumed by teams or projects outside your immediate solution.

🏏

Cricket analogy: A ProjectReference is like two teams from the same academy training together on the same ground, adjusting instantly to each other's changes, while a PackageReference is like signing an overseas player from a scouting report — you get a fixed, versioned skillset, not live access to their training.

Packaging with NuGet

To turn a class library into a distributable NuGet package, the .csproj needs package metadata — PackageId, Version, Authors, Description, and typically PackageLicenseExpression — after which dotnet pack produces a .nupkg file containing the compiled assembly plus metadata. Versioning should follow Semantic Versioning (MAJOR.MINOR.PATCH): increment MAJOR for breaking API changes, MINOR for backward-compatible additions, and PATCH for bug fixes, since consumers' PackageReference version ranges rely on that contract to avoid unexpected breaks.

🏏

Cricket analogy: Semantic versioning is like a player's official rating tiers — a MAJOR bump is like being reclassified from domestic to international level (a fundamentally different contract), while a PATCH is like a minor fitness clearance update that changes nothing about their role.

Publishing and Consuming Packages

Packages can be pushed to the public nuget.org feed with dotnet nuget push, or to a private feed — Azure Artifacts, GitHub Packages, or a self-hosted feed — for internal-only libraries, configured via a NuGet.Config with the feed URL and credentials. Consumers add the dependency with dotnet add package <PackageId> --version <x.y.z>, and pinning explicit versions (or using version ranges deliberately) in the .csproj avoids the classic diamond-dependency problem, where two transitive dependencies require incompatible versions of the same package.

🏏

Cricket analogy: Publishing to a private feed like Azure Artifacts is like an academy keeping its custom training drills internal to its own age-group squads rather than sharing them with rival franchises on the public circuit.

xml
<!-- MyCompany.Utilities.csproj -->
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>net8.0</TargetFramework>
    <PackageId>MyCompany.Utilities</PackageId>
    <Version>2.1.0</Version>
    <Authors>MyCompany Platform Team</Authors>
    <Description>Shared validation and formatting helpers.</Description>
    <PackageLicenseExpression>MIT</PackageLicenseExpression>
    <RepositoryUrl>https://github.com/mycompany/utilities</RepositoryUrl>
  </PropertyGroup>

</Project>

<!-- Pack and push -->
<!-- dotnet pack -c Release -o ./nupkgs -->
<!-- dotnet nuget push ./nupkgs/MyCompany.Utilities.2.1.0.nupkg --source https://api.nuget.org/v3/index.json --api-key $NUGET_API_KEY -->

Semantic Versioning (MAJOR.MINOR.PATCH) is a contract, not a formality — consumers often pin version ranges like [2.0.0,3.0.0) expecting no breaking changes within a major version, so bumping MAJOR is the correct signal whenever a public API changes incompatibly.

The diamond-dependency problem occurs when two packages your project depends on require different, incompatible versions of a shared transitive dependency. NuGet resolves this by picking the highest compatible version by default, but silent resolution can still cause runtime binding errors — always check dotnet list package --include-transitive when debugging.

  • A class library compiles to a DLL with no entry point, targeting a TFM like net8.0 or netstandard2.0, meant to be referenced rather than run directly.
  • ProjectReference compiles a library from source within the same solution; PackageReference consumes a versioned, compiled NuGet package.
  • dotnet pack builds a .nupkg from a csproj's PackageId, Version, Authors, and Description metadata.
  • Semantic Versioning (MAJOR.MINOR.PATCH) signals the nature of each release: breaking, additive, or fix-only.
  • dotnet nuget push publishes to nuget.org or to a private feed (Azure Artifacts, GitHub Packages) configured in NuGet.Config.
  • dotnet add package installs a dependency at a specific or ranged version via PackageReference.
  • Pinning explicit versions helps avoid the diamond-dependency problem where transitive dependencies require conflicting versions of the same package.

Practice what you learned

Was this page helpful?

Topics covered

#NET#NETCoreStudyNotes#MicrosoftTechnologies#ClassLibrariesAndNuGetPackages#Class#Libraries#NuGet#Packages#OOP#StudyNotes#SkillVeris