F# And C# Interop
Purpose
Keep mixed F# and C# solutions intentional.
The practical job is to decide which language owns which responsibility, make the project-reference direction explicit, and shape public APIs so each language can call the other without awkward translation code leaking everywhere.
When To Use
- Use this skill when a solution contains both
.fsprojand.csprojprojects. - Use this skill when adding an F# library to a C# app or service.
- Use this skill when adding a C# host or infrastructure project around F# domain code.
- Use this skill when public package APIs need to work well from both F# and C#.
Source Check
Use repo-local .NET files, checked-out dependency sources, Dash MCP or Dash HTTP for installed .NET docsets, and then official Microsoft documentation when Dash/local coverage is missing or stale:
Boundary Workflow
- Inspect project graph:
rg --files -g '*.fsproj' -g '*.csproj' -g '*.sln' -g '*.slnx' - Identify the language-owned responsibilities:
- F# domain library
- C# host app
- C# ASP.NET Core service
- F# CLI
- shared abstractions
- tests
- Choose dependency direction.
- Shape the API at the boundary.
- Add tests from the consuming side when the boundary is public or fragile.
- Run solution-level build and tests.
Good Mixed Shapes
Common durable shapes:
- F# domain library consumed by a C# app or ASP.NET Core host
- F# transformation package consumed by C# tools
- C# infrastructure adapter consumed by an F# application
- shared test projects that verify public package behavior from both languages
Avoid mixed language solutions when the only reason is indecision. Ask for a language choice instead.
API Boundary Notes
When C# consumes F#:
- be careful with options, tuples, curried functions, and discriminated unions
- expose C#-friendly functions or classes only at the boundary that needs them
- keep the idiomatic F# API intact when F# callers still matter
When F# consumes C#:
- handle nullability explicitly
- wrap exception-heavy APIs into clearer result shapes when useful
- keep async/task conversion visible at the edge
Project References
Use dotnet add reference or direct project-file edits that match the repo style.
After reference changes, run:
dotnet build
dotnet test
Use package references instead of project references only when the dependency is intentionally versioned and released independently.
Output Shape
Return:
Boundary: which language owns which project.Dependency direction: project references and rationale.API shape: F#-native, C#-friendly, or dual surface.Interop risks: nullability, options, unions, async/task, file ordering.Validation: exact build and test commands.
Guardrails
- Do not add mixed language projects without naming the concrete benefit.
- Do not flatten F# domain modeling into C#-shaped classes just for convenience.
- Do not expose awkward F# internals to C# consumers when a small boundary API would be clearer.
- Do not hide nullability or task conversion problems.
- Do not change dependency direction casually in an existing solution.