Four decisions that are not the same version
C# is the language; the SDK supplies tools for building projects; the target framework defines the API contract; the runtime executes the published application. This path uses C# 14 and .NET 10 as an explicit baseline. In a project, net10.0 is a TFM rather than a full SDK version number. global.json can control SDK selection without automatically rewriting TargetFramework.
Language version normally follows the target supported by the SDK, but changing only LangVersion does not install tools or add runtime APIs. When an error arrives, record the phase in which it occurred: tool selection, restore, compilation, publication or startup.
A missing-SDK message needs different evidence from an exception raised by the program after startup.
Inventory tools in the right context
dotnet --list-sdks shows installed SDKs visible in the invoked architecture context; --list-runtimes shows runtimes. By contrast, --version reports the SDK selected for commands in that context. Working directory matters: the CLI searches for global.json from there and through ancestor directories.
Do not compare results from different folders without recording that difference. A file pinning version 10.0.100 with rollForward disable requires that exact version; finding only 10.0.101 does not satisfy the policy. A team may choose another policy, but should document it and control updates. global.json is not an installer and should not be confused with the application’s runtimeconfig file.
Installing tools on a development machine also does not prove they are available on a separate CI agent.
Restore the graph and control changes
Restore resolves project dependencies, including transitive ones. NuGet produces obj/project.assets.json, which supplies the SDK with compilation information. If it is absent in a clean checkout, do not replace it with empty JSON: inspect restore and the first failure that prevented it.
For an application with approved dependencies, packages.lock.json records the graph. Ordinary restore can update it when inputs change; mere file presence does not prohibit changes. In locked mode, a change inconsistent with the locked graph should make the operation fail.
A deliberate update requires reviewing dependencies and the new lock before accepting them. The SDK can also influence implicit references. Retaining the SDK and graph helps control inputs but does not prove equality of every byte, external service or runtime result.
The project includes items by convention
In an SDK-style project, many C# files already enter the Compile set by convention. Adding Include for the same file without changing defaults can produce NETSDK1022. If you want to keep the convention, remove the redundant entry and retain the file.
If you maintain a fully explicit list, the policy needs to disable the corresponding default inclusion. This is a decision about project items, not a class name. For an already included None file, Update changes its metadata without adding it again.
Also distinguish output from publish: CopyToOutputDirectory can place a file beside a local build while CopyToPublishDirectory Never excludes publication copying. Delivery evidence is the content of the artifact that will be distributed, not just what exists in the repository.
Choose the delivery contract
A framework-dependent publication uses a compatible runtime available at the destination. Self-contained mode includes required .NET files but remains tied to the platform and native system dependencies. Use explicit options when comparing the two modes.
The linux-x64 RID describes the destination; it does not make --self-contained false independent of a runtime. Bundling a runtime copy also transfers servicing responsibility: updating a separate global runtime does not automatically update the application’s distributed copy.
Accepted startup needs to consider required configuration, architecture, files and permissions. Do not turn the existence of an executable into proof of universal compatibility. For an incident, retain the delivered package identity and compare it with the approved one; diagnosing another build can lead to an irrelevant conclusion.
Trimming warnings need interpretation
Trimming removes code considered unnecessary to reduce publication size. Some reflection patterns depend on types or members that static analysis cannot determine. A warning about such a path identifies an investigation obligation; suppressing it does not declare which members need to survive.
When types are known, appropriate preservation requirements can make the design analyzable. In other cases, making selection explicit, using suitable generation or choosing publication without that reduction is preferable. Do not conclude that all reflection is invalid or that any successful compilation proves compatibility after trimming.
Relate the warning to the paths the application uses and evidence from the produced artifact. These concepts explain diagnostics; questions ask for interpretation and selection without executing code.
Guided reading: deliver only required configuration
The illustrated project already contains Program.cs, rules.json and notes.local.json. The JSON files are None items included by SDK conventions. rules.json is required destination configuration; notes.local.json contains local notes outside the product.
Update sets distinct policies without duplicating items. For a new publication folder and no additional copy steps, rules.json is eligible for copying and notes.local.json is excluded. EnableDefaultCompileItems has not been disabled: there is no need to add Program.cs with Include.
This XML does not pin the complete SDK, include a runtime by itself or guarantee the semantic content of rules.json. Review should check the project contract and package inventory. In assessments, read the XML and select the explanation consistent with the supplied facts.
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<None Update="rules.json"
CopyToOutputDirectory="PreserveNewest"
CopyToPublishDirectory="PreserveNewest" />
<None Update="notes.local.json"
CopyToOutputDirectory="Never"
CopyToPublishDirectory="Never" />
</ItemGroup>
</Project>A local build containing rules.json is not enough: the contract must include it in publication while keeping local notes out.
Common pitfalls
Confusing TFM with SDK; fabricating project.assets.json; treating lock presence as locked mode; duplicating Compile; delivering output as though it were publish; suppressing warnings without resolving requirements.
Related topics: Exceptions, resources and files · Decimals, time and data contracts · Async tasks and cancellation
Diagnose the right phase using the SDK, graph and artifact actually used. Building, publishing and running provide different evidence.
Reference: SDK selection with global.json · C# 14 / .NET 10; no preview features