Question
Class Library (.NET Core) vs .NET Standard in C#: Differences, Use Cases, and When to Choose Each
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>
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
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
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
- The project says it targets
netstandard2.0. - The compiler checks that your code only uses APIs allowed by .NET Standard 2.0.
- The library is built as a DLL.
- A compatible app, such as a .NET Framework app or a .NET Core app that supports .NET Standard 2.0, can reference that DLL.
- When the app runs, the provides the actual implementation of those APIs.
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.0test project can reference anetstandard2.0library - a
.NET Frameworktest project cannot reference a library that only targetsnet8.0
Migration scenario
Suppose a company is moving from .NET Framework to modern .NET.
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 ()
{
;
;
}
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.
Comparisons
| Target | What it is | Best for | Main limitation |
|---|---|---|---|
| .NET Framework class library | Library for the older Windows-focused .NET Framework | Maintaining older applications | Not suitable for modern cross-platform development |
| .NET Standard class library | API contract shared by multiple .NET implementations | Reusable libraries with broad compatibility | Limited to APIs in the chosen standard |
| .NET Core / modern .NET class library | Library for a specific runtime implementation | App-specific libraries and modern features | Less compatible with older platforms |
.NET Standard vs .NET Core target
| Feature | .NET Standard | .NET Core / modern .NET |
|---|---|---|
| Is it a runtime? | No |
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 Standardwhen many different .NET platforms must consume the library - Use
net6.0+/net8.0when 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 →
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.
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
- Create a class library with a method that formats a user's full name.
- Target the library with
netstandard2.0first. - Add a console app that references the library and calls the method.
- Change the library target to
net8.0and observe that it now assumes a modern runtime. - Optionally multi-target the library with both
netstandard2.0andnet8.0.
Keep learning
Related questions
AddTransient vs AddScoped vs AddSingleton in ASP.NET Core Dependency Injection
Learn the differences between AddTransient, AddScoped, and AddSingleton in ASP.NET Core DI with examples and practical usage.
Best Way to Repeat a Character in C#: Building Repeated Strings Efficiently
Learn the best way to repeat a character in C#, compare StringBuilder, string concatenation, and simpler built-in options.
C# Array Initialization Syntaxes Explained
Learn all common C# array initialization syntaxes with examples, rules, comparisons, and mistakes beginners often make.