Engineering

.NET Framework on Windows, in VS Code: run and test net48 next to net10.0, in C# and F#

An emerald ribbon connects a stone window and a modern green arch, representing .NET Framework and modern .NET working together

The .NET that gets the conference talks is net10.0. A lot of the .NET that ships every day is net48: the line-of-business app, the WCF service, the WinForms tool the finance team can't do without, the library with one important customer still on 4.6.2. It runs on Windows, it isn't going away, and it is often the reason a team's editor was chosen for them.

Since 0.23, SharpLsp has analysed .NET Framework code on every OS. On Windows it does the rest of the job too: it builds, runs and tests that code on the real desktop CLR, in C# and F#, from the editor you already use.

Why this matters

For many teams, the .NET Framework projects are the last reason everyone has to use the same IDE. They don't have to be.

What works on Windows

What SharpLsp does Where
Edit Completions, navigation, diagnostics and refactoring inside #if NETFRAMEWORK code, against the 4.x reference assemblies every OS
Build dotnet build, with reference assemblies the SDK pulls from NuGet. No targeting packs to install every OS
Run Ctrl+F5 runs a multi-targeted project's net48 build on the desktop CLR Windows
Test VSTest and MTP discover and run every framework, with one result per framework Windows
Debug Refused with a reason; debug the .NET build of the same code —

All of it covers net462 through net481. The in-place 4.8.x runtime in current Windows 10 and 11 runs every one of them.

Run: Ctrl+F5 on the desktop CLR

In a multi-targeted project, pick net48 in the status bar, then press Ctrl+F5. SharpLsp starts the task Run <Project> (net48):

dotnet run --project  --framework net48

There is no debug adapter in the way, so the build starts on the CLR it was compiled for. The framework comes from the status bar when you launch from one of the project's files, and otherwise it is the first one the project declares. Either way the output channel logs Run: <Project> under net48.

Test: every framework, one tree

A multi-targeted test project appears once in the Test Explorer, with its frameworks on the row: net48 · net10.0.

MTP on .NET Framework: an .exe, not a .dll

A Microsoft.Testing.Platform module for .NET is a .dll that dotnet exec hosts. For .NET Framework, MSBuild produces a real <Name>.exe, and dotnet exec can't host the desktop CLR, so SharpLsp starts the .exe directly. It is tested with MSTest in C# and xUnit v3 in F#, each on net48 beside .NET.

F#, on the same terms

None of this is C#-only. An F# project's net48 options come from MSBuild's own design-time compile: --targetprofile:mscorlib, the 4.8 reference assemblies and the NETFRAMEWORK;NET48 defines. F# test projects run on net48 under VSTest and MTP, and Ctrl+F5 runs an F# program on the desktop CLR exactly as it runs a C# one.

What it doesn't do yet: debug the desktop CLR

SharpLsp ships netcoredbg, an open-source debugger for .NET. It doesn't attach to the .NET Framework desktop CLR, and a test host waiting for a debugger that never arrives just hangs. So SharpLsp doesn't let that happen:

During a migration that is usually enough: debug on net10.0, run and test on net48.

Built on Windows, tested on Windows

Try it

  1. Install SharpLsp 0.25 or later from the VS Code Marketplace or Open VSX, in VS Code, Cursor, Windsurf or VSCodium.
  2. Open a solution with an SDK-style project that targets a .NET Framework version and a .NET one, for example net48;net10.0. You need the .NET 10 SDK and nothing else: no targeting packs, no IDE install.
  3. Pick net48 in the status bar, press Ctrl+F5, and open the Test Explorer.

The whole stack is MIT licensed: no account, no seat count, nothing to renew. If your .NET Framework project doesn't behave, open an issue. Real project files from real migrations are exactly what we want to see.