Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
171 changes: 171 additions & 0 deletions docs/standard/library-guidance/dependencies-net.md
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You'll need to add this to the TOC somewhere. Maybe below this article in this file?

Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
---
ai-usage: ai-assisted
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
ai-usage: ai-assisted
title: Guidance for .NET library dependency versions
description: Learn about the strategies and recommendations for choosing dependency versions when you build a library that targets multiple .NET versions.
ms.date: 11/24/2025
ai-usage: ai-assisted

---
# Guidance for .NET library dependency versions

When building libraries that target multiple .NET versions, choosing dependency versions has implications for compatibility, servicing, and ecosystem health. This document outlines the main strategies, their tradeoffs, and recommendations.

---
Comment on lines +6 to +8
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
When building libraries that target multiple .NET versions, choosing dependency versions has implications for compatibility, servicing, and ecosystem health. This document outlines the main strategies, their tradeoffs, and recommendations.
---
When you build libraries that target multiple .NET versions, choosing dependency versions has implications for compatibility, servicing, and ecosystem health. This article outlines the main strategies, their tradeoffs, and recommendations.


## **Overview**
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
## **Overview**
## Overview


Library authors face challenges when deciding which version of a .NET dependency to reference. Newer versions have more API and features, but may require a local redistribution increasing servicing responsibilities of the library and size of the application. The decision impacts:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Library authors face challenges when deciding which version of a .NET dependency to reference. Newer versions have more API and features, but may require a local redistribution increasing servicing responsibilities of the library and size of the application. The decision impacts:
Library authors face challenges when deciding which version of a .NET dependency to reference. Newer versions have more APIs and features but might require a local redistribution, which increases servicing responsibilities of the library and size of the application. The decision impacts:


- **Friction of updates** on older runtimes. Friction of updates are any of the cost associated with taking a new version of a libary. This could be the set of changes introduced between major versions of a dependency, the application size due to more app-local dependencies, the application startup performance due to using app-local dependencies without pre-generated native images, etc.
- **Development cost** in maintaining the solution. Here development cost is the cost of doing work in the latest codebase. This might be managing more complex projects with conditions, the total number of dependencies that need to be updated regularly, the cost of maintaining local source to account for missing features in older dependencies (polyfills), dealing with ifdefs around inconsistent API/features across versions, etc.

Check failure on line 15 in docs/standard/library-guidance/dependencies-net.md

View workflow job for this annotation

GitHub Actions / lint

Trailing spaces

docs/standard/library-guidance/dependencies-net.md:15:435 MD009/no-trailing-spaces Trailing spaces [Expected: 0 or 2; Actual: 1] https://github.com/DavidAnson/markdownlint/blob/v0.39.0/doc/md009.md
Comment on lines +14 to +15
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
- **Friction of updates** on older runtimes. Friction of updates are any of the cost associated with taking a new version of a libary. This could be the set of changes introduced between major versions of a dependency, the application size due to more app-local dependencies, the application startup performance due to using app-local dependencies without pre-generated native images, etc.
- **Development cost** in maintaining the solution. Here development cost is the cost of doing work in the latest codebase. This might be managing more complex projects with conditions, the total number of dependencies that need to be updated regularly, the cost of maintaining local source to account for missing features in older dependencies (polyfills), dealing with ifdefs around inconsistent API/features across versions, etc.
- **Friction of updates** on older runtimes. Friction of updates is the cost associated with taking a new version of a library. For example, this friction could be the set of changes introduced between major versions of a dependency, the application size due to more app-local dependencies, or the application startup performance due to using app-local dependencies without pregenerated native images.
- **Development cost** in maintaining the solution, which is the cost of doing work in the latest codebase. For example, this cost might be managing more complex projects with conditions, the total number of dependencies that need to be updated regularly, maintaining local source to account for missing features in older dependencies (polyfills), or dealing with ifdefs around inconsistent APIs or features across versions.

- **Servicing cost** in managing supported releases. Here servicing cost is the ongoing cost in keeping release branches building, up to date, and compliant with all supported build tools.

This guidance provides options, tradeoffs, and a decision matrix to help you choose the best approach.
✔️ DO be deliberate in choosing a dependency policy for your library
✔️ DO remove out of support target frameworks from your package.
✔️ CONSIDER choosing an approach that minimizes development costs and adjusting based on customer feedback
✔️ CONSIDER changing your approach through the lifecycle of your library
❌ DO NOT assume any policy is incorrect, all policies are technically sound and supported with different tradeoffs for consumption

---
Comment on lines +19 to +25
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
✔️ DO be deliberate in choosing a dependency policy for your library
✔️ DO remove out of support target frameworks from your package.
✔️ CONSIDER choosing an approach that minimizes development costs and adjusting based on customer feedback
✔️ CONSIDER changing your approach through the lifecycle of your library
❌ DO NOT assume any policy is incorrect, all policies are technically sound and supported with different tradeoffs for consumption
---
✔️ DO be deliberate in choosing a dependency policy for your library.
✔️ DO remove out of support target frameworks from your package.
✔️ CONSIDER choosing an approach that minimizes development costs and adjusting based on customer feedback.
✔️ CONSIDER changing your approach through the lifecycle of your library.
❌ DO NOT assume any policy is incorrect, all policies are technically sound and supported with different tradeoffs for consumption.


## **Strategies for Dependency Version Selection**
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
## **Strategies for Dependency Version Selection**
## Strategies for dependency version selection


### **Option 1: Latest supported versions**
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
### **Option 1: Latest supported versions**
### Option 1: Latest supported versions


Reference the latest supported version of the dependency across all target frameworks. For example at the time this document was written when .NET 10.0 is the latest current release a project would reference reference 10.0 packages on `netstandard2.0`, `net8.0`, `net9.0`, and `net10.0`.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Reference the latest supported version of the dependency across all target frameworks. For example at the time this document was written when .NET 10.0 is the latest current release a project would reference reference 10.0 packages on `netstandard2.0`, `net8.0`, `net9.0`, and `net10.0`.
Reference the latest supported version of the dependency across all target frameworks. For example, if .NET 10.0 is the latest current release, a project would reference 10.0 packages on `netstandard2.0`, `net8.0`, `net9.0`, and `net10.0`.

> **Note:** The lifetime of the latest Short-Term Support (STS) release now aligns with the latest LTS release, so choosing “latest” effectively means choosing the latest supported version regardless of STS or LTS designation.
> **Note:** Not all packages support targeting older frameworks in their latest version. For example: `Microsoft.AspNetCore.Authorization`. These packages must be excluded from this policy and must always follow option 2 when used.
Comment on lines +32 to +33
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
> **Note:** The lifetime of the latest Short-Term Support (STS) release now aligns with the latest LTS release, so choosing “latest” effectively means choosing the latest supported version regardless of STS or LTS designation.
> **Note:** Not all packages support targeting older frameworks in their latest version. For example: `Microsoft.AspNetCore.Authorization`. These packages must be excluded from this policy and must always follow option 2 when used.
> [!NOTE]
> - The lifetime of the latest Short-Term Support (STS) release now aligns with the latest LTS release, so choosing “latest” effectively means choosing the latest supported version regardless of STS or LTS designation.
> - Not all packages support targeting older frameworks in their latest version, for example, `Microsoft.AspNetCore.Authorization`. If you reference these packages, exclude them from this policy and follow option 2 instead.


**Pros**

- Simplifies decision-making and aligns with stability expectations.
- Reduces complexity in dependency management.
- Encourages modernization and enables access to new features.

**Cons**

- Users targeting older TFMs will get the latest version's behavior, including any potential breaking changes.
- Larger number of packages to update in comparison to relying on framework provided API.
- May create friction for customers who are not on the latest supported runtime.
- May prevent consumption in enviroments where dependencies are managed by a host application - eg: MSBuild, Visual Studio, Azure Functions V1.
Comment on lines +45 to +46
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
- May create friction for customers who are not on the latest supported runtime.
- May prevent consumption in enviroments where dependencies are managed by a host application - eg: MSBuild, Visual Studio, Azure Functions V1.
- Might create friction for customers who aren't on the latest supported runtime.
- Might prevent consumption in environments where dependencies are managed by a host application - for example, MSBuild, Visual Studio, and Azure Functions V1.


---

### **Option 2: TFM-specific versions**
Comment on lines +48 to +50
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
---
### **Option 2: TFM-specific versions**
### Option 2: TFM-specific versions


Reference different dependency versions per Target Framework Moniker (TFM). Don't reference packages when the framework provides the API. For example: 8.0 packages on `net8.0`, 9.0 packages on `net9.0`, and so on.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Reference different dependency versions per Target Framework Moniker (TFM). Don't reference packages when the framework provides the API. For example: 8.0 packages on `net8.0`, 9.0 packages on `net9.0`, and so on.
Reference different dependency versions per Target Framework Moniker (TFM). Don't reference packages when the framework provides the API. For example, reference 8.0 packages on `net8.0`, 9.0 packages on `net9.0`, and so on.


**Pros**

- Minimizes change for apps running on older runtimes.
- Minimizes app-local libraries app size on older runtimes. Uses runtime optimized library on older runtimes.

**Cons**

- Reduced API available to library which may lead to more complex implementations (polyfills). Polyfills increase the total cost of devlopment and servicing.
- Slows innovation in libraries.
- Greater complexity of infrastructure to maintain seperate dependency sets per target framework. Central packagge management and dependabot can be be configured to work with this, but it's challenging to get it right.
Comment on lines +61 to +63
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
- Reduced API available to library which may lead to more complex implementations (polyfills). Polyfills increase the total cost of devlopment and servicing.
- Slows innovation in libraries.
- Greater complexity of infrastructure to maintain seperate dependency sets per target framework. Central packagge management and dependabot can be be configured to work with this, but it's challenging to get it right.
- Reduced API surface available to library, which might lead to more complex implementations (polyfills). Polyfills increase the total cost of development and servicing.
- Slows innovation in libraries.
- Greater complexity of infrastructure to maintain separate dependency sets per target framework. You can configure central package management and dependabot to work with this, but it's challenging to get it right.


---

### **Option 3: Branching**
Comment on lines +65 to +67
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
---
### **Option 3: Branching**
### Option 3: Branching


Reference the latest supported version of the dependency across all target frameworks, but create a new release branch your when you change major versions of dependencies. This is the same as Option 1, but retaining a supported branch allows consumers to choose between staying on older supported packages or getting new features.

❌ AVOID using branching as a way to remove support for in-support frameworks as this removes the feature/support choice for users.
Comment on lines +69 to +71
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Reference the latest supported version of the dependency across all target frameworks, but create a new release branch your when you change major versions of dependencies. This is the same as Option 1, but retaining a supported branch allows consumers to choose between staying on older supported packages or getting new features.
❌ AVOID using branching as a way to remove support for in-support frameworks as this removes the feature/support choice for users.
Reference the latest supported version of the dependency across all target frameworks, but create a new release branch when you change major versions of dependencies. This is the same as Option 1, but retaining a supported branch allows consumers to choose between staying on older supported packages or getting new features.
❌ AVOID using branching as a way to remove support for in-support frameworks as this approach removes the feature/support choice for users.


**Pros**

- Balances compatibility with flexibility for fast-moving areas.
- Encourages modernization and access to new features.
- Simplifies decision-making and aligns with stability expectations.
- Reduces complexity in dependency management.

**Cons**

- Increased infrastructure cost to maintain concurrent branches.
- No new features for folks who want to stay on older dependencies.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
- No new features for folks who want to stay on older dependencies.
- No new features for consumers who want to stay on older dependencies.


---

## **Decision matrix**
Comment on lines +85 to +87
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
---
## **Decision matrix**
## Decision matrix


| Strategy | Update Friction | Development Cost | Servicing Cost |
|---------------------------------------|------------------|------------------|------------------|
| Latest Supported Versions (Option 1) | Moderate | Low | Moderate |
| TFM-Specific Versions (Option 2) | Low | High | Low / Moderate |
| Branching | Low | Low | High |

---

## **Key tradeoffs**

- **Friction vs. Innovation:** Latest versions offer new features but have more friction for existing applications on older runtimes.
- **Development cost:** Configuring multiple dependency groups, dependency updates for those, and managing API gaps can all accumulate to slow down innovation in a fast moving library.
Comment on lines +95 to +100
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
---
## **Key tradeoffs**
- **Friction vs. Innovation:** Latest versions offer new features but have more friction for existing applications on older runtimes.
- **Development cost:** Configuring multiple dependency groups, dependency updates for those, and managing API gaps can all accumulate to slow down innovation in a fast moving library.
## Key tradeoffs
- **Friction vs. innovation:** Latest versions offer new features but have more friction for existing applications on older runtimes.
- **Development cost:** Configuring multiple dependency groups, updating dependencies for those, and managing API gaps can all accumulate to slow down innovation in a fast-moving library.

- **Servicing cost:** More package dependencies means more updates. More branches mean more concurrent builds that need to stay healthy. Additional code in the form of polyfills means more LOC with potential bugs.

---

## **Recommended guidance**
Comment on lines +103 to +105
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
---
## **Recommended guidance**
## Recommended guidance


- Option 1: Recommended for libraries moving fast and undergoing lots of innovation and feature development. Consumers are more likely to be on the latest framework.
- Option 2: Recommended for libraries that are stable and do not require new features. Could be a transition strategy when a library reaches maturity and stops taking large features.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
- Option 2: Recommended for libraries that are stable and do not require new features. Could be a transition strategy when a library reaches maturity and stops taking large features.
- Option 2: Recommended for libraries that are stable and don't require new features. Could be a transition strategy when a library reaches maturity and stops taking large features.

- Option 3: Recommended for libraries that are part of .NET or receive strong customer feedback that option 1 is limiting consumption or blocking adoption. Could be a transition strategy when Option 1 has too much friction.

---

## **Examples**
Comment on lines +111 to +113
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
---
## **Examples**
## Examples


### Example 1: Latest supported version

Check failure on line 115 in docs/standard/library-guidance/dependencies-net.md

View workflow job for this annotation

GitHub Actions / lint

Headings should be surrounded by blank lines

docs/standard/library-guidance/dependencies-net.md:115 MD022/blanks-around-headings Headings should be surrounded by blank lines [Expected: 1; Actual: 0; Below] [Context: "### Example 1: Latest supported version"] https://github.com/DavidAnson/markdownlint/blob/v0.39.0/doc/md022.md
```xml

Check failure on line 116 in docs/standard/library-guidance/dependencies-net.md

View workflow job for this annotation

GitHub Actions / lint

Fenced code blocks should be surrounded by blank lines

docs/standard/library-guidance/dependencies-net.md:116 MD031/blanks-around-fences Fenced code blocks should be surrounded by blank lines [Context: "```xml"] https://github.com/DavidAnson/markdownlint/blob/v0.39.0/doc/md031.md
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
```xml
```xml

<PropertyGroup>
<TargetFrameworks>net8.0;net9.0;net10.0</TargetFrameworks>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="System.IO.Packaging" Version="10.0.0" />
<PackageReference Include="System.Text.Json" Version="10.0.0" />
</ItemGroup>
```

### Example 2: TFM-specific versions

Check failure on line 126 in docs/standard/library-guidance/dependencies-net.md

View workflow job for this annotation

GitHub Actions / lint

Headings should be surrounded by blank lines

docs/standard/library-guidance/dependencies-net.md:126 MD022/blanks-around-headings Headings should be surrounded by blank lines [Expected: 1; Actual: 0; Below] [Context: "### Example 2: TFM-specific versions"] https://github.com/DavidAnson/markdownlint/blob/v0.39.0/doc/md022.md
```xml

Check failure on line 127 in docs/standard/library-guidance/dependencies-net.md

View workflow job for this annotation

GitHub Actions / lint

Fenced code blocks should be surrounded by blank lines

docs/standard/library-guidance/dependencies-net.md:127 MD031/blanks-around-fences Fenced code blocks should be surrounded by blank lines [Context: "```xml"] https://github.com/DavidAnson/markdownlint/blob/v0.39.0/doc/md031.md
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
```xml
```xml

<PropertyGroup>
<TargetFrameworks>net8.0;net9.0;net10.0</TargetFrameworks>
</PropertyGroup>
<ItemGroup>
<PackageReference Condition="'$(TargetFramework)' == 'net8.0'" Include="System.IO.Packaging" Version="8.0.0" />
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry I wasn't clear earlier about the example.

I think a good example here would be Microsoft.Extensions.Logging.Abstractions. It isn't in the shared framework, but lifts a dependency on a library that is in the shared framework.

https://www.nuget.org/packages/Microsoft.Extensions.Logging.Abstractions/#dependencies-body-tab

Image

If a library references the 10.0.0 version of Microsoft.Extensions.Logging.Abstractions, System.Diagnostics.DiagnosticSource will be lifted out of the shared framework for all net8 and net9 apps that reference the library.

<PackageReference Condition="'$(TargetFramework)' == 'net9.0'" Include="System.IO.Packaging" Version="9.0.0" />
<PackageReference Condition="'$(TargetFramework)' == 'net10.0'" Include="System.IO.Packaging" Version="10.0.0" />
<!-- Note that System.Text.Json is absent as it is provided by the framework -->
</ItemGroup>
```

### Example 3: Branching

Check failure on line 139 in docs/standard/library-guidance/dependencies-net.md

View workflow job for this annotation

GitHub Actions / lint

Headings should be surrounded by blank lines

docs/standard/library-guidance/dependencies-net.md:139 MD022/blanks-around-headings Headings should be surrounded by blank lines [Expected: 1; Actual: 0; Below] [Context: "### Example 3: Branching"] https://github.com/DavidAnson/markdownlint/blob/v0.39.0/doc/md022.md
Branch: release/8.0
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Branch: release/8.0
Branch: release/8.0

```xml

Check failure on line 141 in docs/standard/library-guidance/dependencies-net.md

View workflow job for this annotation

GitHub Actions / lint

Fenced code blocks should be surrounded by blank lines

docs/standard/library-guidance/dependencies-net.md:141 MD031/blanks-around-fences Fenced code blocks should be surrounded by blank lines [Context: "```xml"] https://github.com/DavidAnson/markdownlint/blob/v0.39.0/doc/md031.md
<PropertyGroup>
<TargetFrameworks>net6.0;net7.0;net8.0</TargetFrameworks>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="System.IO.Packaging" Version="8.0.0" />
<PackageReference Include="System.Text.Json" Version="8.0.0" />
</ItemGroup>
```

Branch: release/9.0
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Branch: release/9.0
Branch: release/9.0

```xml

Check failure on line 152 in docs/standard/library-guidance/dependencies-net.md

View workflow job for this annotation

GitHub Actions / lint

Fenced code blocks should be surrounded by blank lines

docs/standard/library-guidance/dependencies-net.md:152 MD031/blanks-around-fences Fenced code blocks should be surrounded by blank lines [Context: "```xml"] https://github.com/DavidAnson/markdownlint/blob/v0.39.0/doc/md031.md
<PropertyGroup>
<TargetFrameworks>net8.0;net9.0</TargetFrameworks>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="System.IO.Packaging" Version="9.0.0" />
<PackageReference Include="System.Text.Json" Version="9.0.0" />
</ItemGroup>
```

Branch: release/10.0
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Branch: release/10.0
Branch: release/10.0

```xml

Check failure on line 163 in docs/standard/library-guidance/dependencies-net.md

View workflow job for this annotation

GitHub Actions / lint

Fenced code blocks should be surrounded by blank lines

docs/standard/library-guidance/dependencies-net.md:163 MD031/blanks-around-fences Fenced code blocks should be surrounded by blank lines [Context: "```xml"] https://github.com/DavidAnson/markdownlint/blob/v0.39.0/doc/md031.md
<PropertyGroup>
<TargetFrameworks>net8.0;net9.0;net10.0</TargetFrameworks>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="System.IO.Packaging" Version="10.0.0" />
<PackageReference Include="System.Text.Json" Version="10.0.0" />
</ItemGroup>
```
Loading