Question

Class Library (.NET Core) vs .NET Standard in C#: Differences, Use Cases, and When to Choose Each

csharp.net.net-core.net-standardclass-library

Question

In Visual Studio, there are several class library project types you can create:

  • Class Library (.NET Framework)
  • Class Library (.NET Standard)
  • Class Library (.NET Core)

The traditional Class Library (.NET Framework) project type has existed for a long time, but it is often unclear when to choose Class Library (.NET Standard) versus Class Library (.NET Core).

This confusion becomes more noticeable when working with:

  • multi-targeting different framework versions
  • unit test projects
  • sharing code across different .NET implementations

What is the difference between Class Library (.NET Standard) and Class Library (.NET Core), why do both project types exist, and when should each one be used?

Short Answer

By the end of this page, you will understand that .NET Standard is an API specification for code sharing across multiple .NET platforms, while .NET Core is a concrete runtime and implementation. You will learn why both library types existed, how compatibility works, and how to decide which library target to use in real C# projects.

Concept

.NET Standard and .NET Core solve related but different problems.

  • .NET Core is a real .NET implementation and runtime.
  • .NET Standard is a set of APIs that multiple .NET implementations agree to support.

A useful way to think about it is:

  • .NET Core = the engine you run
  • .NET Standard = the common contract different engines agree to follow

What a .NET Core class library is

A Class Library (.NET Core) targets a specific .NET implementation, such as:

<TargetFramework>netcoreapp3.1</TargetFramework>

or in modern .NET:

<TargetFramework>net8.0</TargetFramework>

When you target a concrete framework, your library can use APIs available in that framework. That gives you access to implementation-specific features, but your library only works on platforms that support that target.

What a .NET Standard class library is

A Class Library (.NET Standard) targets a standard API surface, such as:

<TargetFramework>netstandard2.0</TargetFramework>

This means:

  • your library promises to use only APIs defined by that standard
  • any runtime that implements that standard can use your library

That made .NET Standard useful for reusable libraries that needed to work across:

  • .NET Framework
  • .NET Core
  • Xamarin
  • Mono
  • later versions of .NET

Why both existed

They existed because developers had two different goals:

  1. Maximum compatibility across .NET platforms → target .NET Standard
  2. Access to a specific runtime's features → target .NET Core

If you were building a shared utility package to be consumed by many different app types, .NET Standard was often the better choice.

If you were building a library only for a .NET Core app and wanted newer runtime APIs, performance features, or platform-specific capabilities, targeting .NET Core made more sense.

Important modern context

In modern .NET, the guidance has changed:

  • If you need to support old cross-platform combinations, especially including .NET Framework, use .NET Standard.
  • If you are building for modern .NET only (for example .NET 6, 7, or 8), usually target net6.0, net7.0, or net8.0 directly.

That is because modern .NET unifies many older ideas, and .NET Standard is no longer the default choice for new libraries unless broad compatibility is required.

Why this matters

Choosing the wrong target can cause problems such as:

  • not being able to reference the library from another project
  • missing APIs you expected to use
  • unit test project compatibility issues
  • unnecessary restrictions on your code

So the real decision is not "Which one is newer?" but:

Do I need broad compatibility, or do I need specific runtime features?

Mental Model

Imagine you are writing instructions for kitchen appliances.

  • .NET Standard is like writing a recipe that works with any oven model as long as the oven supports a common set of features.
  • .NET Core is like writing a recipe specifically for one advanced smart oven with extra features.

If you want many kitchens to use your recipe, keep it generic and follow the shared standard.

If you know the exact oven your kitchen uses, you can take advantage of its special features.

That is the difference:

  • .NET Standard favors portability
  • .NET Core favors capability on a specific platform
Take Quiz

Syntax and Examples

A class library target is usually defined in the project file.

Example: .NET Standard library

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>netstandard2.0</TargetFramework>
  </PropertyGroup>
</Project>

This library can be referenced by many runtimes that support .NET Standard 2.0.

Example code

namespace SharedUtilities;

public static class TextHelper
{
    public static bool IsBlank(string value)
    {
        return string.IsNullOrWhiteSpace(value);
    }
}

This is a good candidate for .NET Standard because it uses common APIs and could be shared across many projects.

Example: .NET Core or modern .NET library

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>net8.0</TargetFramework>
  </PropertyGroup>
</Project>

This targets a concrete runtime.

Example code

using System.Text.Json;

namespace AppSpecificLibrary;

public static class JsonHelper
{
    public static string ToJson<T>(T value)
    {
        return JsonSerializer.Serialize(value);
    }
}

This kind of library is ideal when you know the consuming application runs on a specific modern .NET version.

Multi-targeting example

Sometimes a library needs both compatibility and newer features.

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFrameworks>netstandard2.0;net8.0</TargetFrameworks>
  </PropertyGroup>
</Project>

This tells the build system to compile the library for both targets.

That can be useful when:

  • older consumers need netstandard2.0
  • newer consumers can use net8.0

Rule of thumb

  • Use netstandard2.0 for broad reuse, especially if .NET Framework support matters.
  • Use net6.0+ / net8.0 when the library is only for modern .NET apps.
  • Avoid targeting older .NET Core app-specific targets unless you truly need them.

Step by Step Execution

Consider this project file:

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>netstandard2.0</TargetFramework>
  </PropertyGroup>
</Project>

And this code:

namespace SharedUtilities;

public static class MathHelper
{
    public static int Double(int value)
    {
        return value * 2;
    }
}

What happens step by step

  1. The project says it targets netstandard2.0.
  2. The compiler checks that your code only uses APIs allowed by .NET Standard 2.0.
  3. The library is built as a DLL.
  4. A compatible app, such as a .NET Framework app or a .NET Core app that supports .NET Standard 2.0, can reference that DLL.
  5. When the app runs, the app's runtime provides the actual implementation of those APIs.

Why that matters

The library itself does not bring its own runtime.

It says, in effect:

"I only depend on APIs from this shared contract. Any compatible .NET runtime can host me."

Now compare with this:

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>net8.0</TargetFramework>
  </PropertyGroup>
</Project>

Step by step:

  1. The project targets net8.0, a specific modern .NET implementation.
  2. The compiler allows APIs available in net8.0.
  3. The built DLL expects to be consumed by a compatible modern .NET application.
  4. Older platforms like .NET Framework cannot use it.

So the target framework controls both:

  • what APIs you can write against
  • which apps can consume your library

Real World Use Cases

When to use .NET Standard

Use .NET Standard when you are building a reusable library that must be shared across different .NET platforms.

Examples:

  • a validation library used by both an old ASP.NET Framework app and a newer .NET app
  • a business rules library shared by desktop, mobile, and server applications
  • a NuGet package intended for a wide range of consumers

When to use .NET Core or modern .NET targets

Use a concrete target like net6.0 or net8.0 when your library is meant for a specific modern application stack.

Examples:

  • an internal library used only by your company's .NET 8 web APIs
  • a library that depends on modern serialization, hosting, or performance APIs
  • a library that uses runtime-specific features not available in .NET Standard

Unit testing scenario

This is a common source of confusion.

If your test project targets a specific runtime, the library under test must be compatible with it.

For example:

  • a net8.0 test project can reference a netstandard2.0 library
  • a .NET Framework test project cannot reference a library that only targets net8.0

Migration scenario

Suppose a company is moving from .NET Framework to modern .NET.

A shared domain library might target netstandard2.0 temporarily so both old and new applications can use the same code during migration.

Take Quiz

Real Codebase Usage

In real projects, developers usually choose the target framework based on who will consume the library.

Common patterns

Shared domain or utility libraries

These often target netstandard2.0 when they must work in multiple environments.

Examples:

  • DTO libraries
  • validation helpers
  • domain models
  • string/date utilities

App-specific libraries

These often target the same framework as the main application.

Examples:

  • data access libraries for a .NET 8 API
  • infrastructure libraries using app configuration or logging packages
  • services tightly coupled to a specific hosting model

Multi-targeting for package authors

NuGet package authors often multi-target to support both old and new consumers.

<TargetFrameworks>netstandard2.0;net8.0</TargetFrameworks>

This pattern helps when:

  • old apps still need compatibility
  • new apps should get the best platform support

Guarding platform-specific code

In multi-targeted codebases, developers sometimes use conditional compilation.

public static string GetPlatformName()
{
#if NET8_0
    return ".NET 8";
#else
    return ".NET Standard compatible runtime";
#endif
}

Keeping libraries clean

A good shared library usually avoids:

  • direct UI dependencies
  • app-host-specific configuration
  • runtime-specific assumptions

That keeps the library portable and easier to test.

Common Mistakes

1. Thinking .NET Standard is a runtime

Mistake: Assuming .NET Standard is something you run.

Reality: It is an API contract, not a runtime.

2. Targeting .NET Standard when you need modern APIs

Broken idea:

<TargetFramework>netstandard2.0</TargetFramework>
// You may expect to use APIs that are only available on newer targets

If your library needs modern runtime-specific features, target a concrete framework like net8.0 instead.

3. Targeting only modern .NET when older consumers still need the library

<TargetFramework>net8.0</TargetFramework>

This is fine for modern apps, but a .NET Framework app cannot consume it.

Avoid this if backward compatibility matters.

4. Confusing compatibility direction

A common misunderstanding is:

  • "If my app is modern, it can reference anything."

Not always. Compatibility depends on the target frameworks and supported API contracts.

A net8.0 app can typically consume netstandard2.0, but a .NET Framework app cannot consume net8.0.

5. Creating unnecessary multi-targets

Beginners sometimes multi-target too early.

<TargetFrameworks>netstandard2.0;net6.0;net7.0;net8.0</TargetFrameworks>

This increases build complexity.

Only multi-target when you have a real compatibility requirement.

6. Using project template names as if they define all behavior

The Visual Studio template name is less important than the target framework in the project file.

Always check:

<TargetFramework>...</TargetFramework>

That is what really controls compatibility.

Comparisons

TargetWhat it isBest forMain limitation
.NET Framework class libraryLibrary for the older Windows-focused .NET FrameworkMaintaining older applicationsNot suitable for modern cross-platform development
.NET Standard class libraryAPI contract shared by multiple .NET implementationsReusable libraries with broad compatibilityLimited to APIs in the chosen standard
.NET Core / modern .NET class libraryLibrary for a specific runtime implementationApp-specific libraries and modern featuresLess compatible with older platforms

.NET Standard vs .NET Core target

Feature.NET Standard.NET Core / modern .NET
Is it a runtime?NoYes
Is it an API contract?YesNo
Broad cross-platform sharingStrongLimited to compatible runtimes
Access to latest platform featuresLimitedBetter
Good for NuGet librariesOften yesYes, if audience is modern only
Good for internal app-only librariesSometimesUsually yes

Simple decision guide

If you need...Choose
Support for .NET Framework and modern .NETnetstandard2.0
Only modern .NET appsnet6.0+ or net8.0
Both old and new consumersMulti-target
Runtime-specific APIsConcrete framework target
Take Quiz

Cheat Sheet

<!-- Broad compatibility -->
<TargetFramework>netstandard2.0</TargetFramework>

<!-- Modern .NET only -->
<TargetFramework>net8.0</TargetFramework>

<!-- Support multiple consumers -->
<TargetFrameworks>netstandard2.0;net8.0</TargetFrameworks>
  • .NET Standard = API contract
  • .NET Core / modern .NET = actual runtime implementation
  • Use .NET Standard when many different .NET platforms must consume the library
  • Use net6.0+ / net8.0 when targeting modern .NET only
  • A modern .NET app can usually reference netstandard2.0
  • A .NET Framework app cannot reference net8.0
  • The real compatibility rule comes from <TargetFramework> in the project file
  • Multi-target only when required

Quick rule

  • Reusable cross-platform library → netstandard2.0
  • Internal modern app library → net8.0
  • Need both → multi-target

Edge case

If you are building a new library and do not need .NET Framework support, prefer a modern target instead of .NET Standard.

FAQ

Is .NET Standard the same as .NET Core?

No. .NET Standard is an API specification, while .NET Core is a real implementation and runtime.

Should I use .NET Standard for all new libraries?

No. Use it only when you need broad compatibility, especially with older platforms like .NET Framework. For modern-only projects, target net6.0+ or later directly.

Can a .NET Core or .NET 8 app reference a .NET Standard library?

Yes, if that runtime supports the chosen .NET Standard version.

Can a .NET Framework app reference a net8.0 library?

No. .NET Framework cannot consume a library that targets net8.0.

Why did Visual Studio show both .NET Standard and .NET Core library templates?

Because they served different goals: shared compatibility versus runtime-specific development.

When should I multi-target a class library?

When different consumers require different targets, such as older .NET Framework apps and newer .NET apps.

Is .NET Standard still relevant?

Yes, mainly for compatibility with older ecosystems. But for new modern-only codebases, direct modern .NET targets are usually preferred.

Take Quiz

Mini Project

Description

Create a small shared utility library and decide how to target it. The project demonstrates the practical difference between building a library for broad compatibility versus building it only for a modern .NET application. This mirrors a real scenario where a team wants one reusable library for multiple apps.

Goal

Build a reusable C# utility library, first as a broadly compatible library and then as a modern-only library, so you can see how target frameworks affect reuse.

Requirements

  1. Create a class library with a method that formats a user's full name.
  2. Target the library with netstandard2.0 first.
  3. Add a console app that references the library and calls the method.
  4. Change the library target to net8.0 and observe that it now assumes a modern runtime.
  5. Optionally multi-target the library with both netstandard2.0 and net8.0.
Take Quiz

Keep learning

Related questions

View more