.NET Integration Guide
January 26, 2026 · View on GitHub
This guide covers compiling TypeScript to .NET DLLs and consuming them from C# projects. Whether you're building TypeScript libraries for .NET applications or integrating TypeScript logic into existing C# codebases, this document provides practical workflows and patterns.
Looking for the reverse? To use existing .NET types (like
StringBuilder,Guid,DateTime) from TypeScript, see Using .NET Types from TypeScript.
Prerequisites
- .NET 10.0 SDK
- SharpTS CLI (installed globally or built from source)
Quick Reference
| Compilation Mode | Command | C# Access Pattern | Best For |
|---|---|---|---|
| Standard | sharpts --compile lib.ts | Reflection (Assembly.LoadFrom) | Dynamic loading, plugins |
| Reference Assembly | sharpts --compile lib.ts --ref-asm | Direct compile-time reference | Strong typing, IntelliSense |
Understanding Compiled Output
Generated Files
When you compile a TypeScript file, SharpTS produces:
| File | Purpose |
|---|---|
<name>.dll | The .NET assembly containing your compiled TypeScript |
<name>.runtimeconfig.json | Runtime configuration for .NET 10.0 |
SharpTS.dll | Runtime dependency (automatically copied to output directory) |
Type Mapping
TypeScript types map to .NET types as follows:
| TypeScript | .NET Type | Notes |
|---|---|---|
number | double | All numbers are IEEE 754 doubles |
string | string | Standard .NET strings |
boolean | bool | Boolean type |
bigint | BigInteger | Arbitrary precision integers |
void | void | No return value |
any, unknown | object | Dynamic types |
null | object | Null reference |
T[] | List<T> | Typed lists (with --ref-asm) |
Promise<T> | Task<T> | Async support |
Map<K,V> | Dictionary<K,V> | Key-value collections |
Set<T> | HashSet<T> | Unique collections |
Date | DateTime | Date/time values |
RegExp | Regex | Regular expressions |
| Classes | .NET classes | Same name as TypeScript class |
Assembly Structure
The compiled assembly organizes code as follows:
- Classes: Emitted as .NET classes (root namespace by default, or custom namespace via
@Namespace) - Top-level functions: Static methods on the
$Programclass - Properties: Accessor methods
get_X()andset_X(value) - Static members: .NET static fields and methods
- Constructors: Standard .NET constructors matching TypeScript signatures
Custom .NET Namespaces
Use the @Namespace decorator to place compiled types in a specific .NET namespace:
@Namespace("MyCompany.Libraries")
class Person {
name: string;
constructor(name: string) {
this.name = name;
}
}
class Employee extends Person {
department: string;
constructor(name: string, department: string) {
super(name);
this.department = department;
}
}
Both Person and Employee will be emitted in the MyCompany.Libraries namespace.
Key points:
- The decorator applies file-wide (all classes in the file use the same namespace)
- Decorators are enabled by default (Stage 3), so no extra flags needed
- Nested namespaces supported:
@Namespace("MyCompany.Libraries.Data") - Without the decorator, classes are emitted at the root namespace (backward compatible)
Project Organization
Recommended Solution Structure
MySolution/
├── MyApp.TypeScript/ # TypeScript source files
│ ├── src/
│ │ ├── models/
│ │ │ └── Person.ts
│ │ └── utils/
│ │ └── Calculator.ts
│ └── compiled/ # Compilation output
│ ├── Library.dll
│ ├── Library.runtimeconfig.json
│ └── SharpTS.dll
├── MyApp.Consumer/ # C# consumer project
│ ├── MyApp.Consumer.csproj
│ └── Program.cs
└── MySolution.sln
Single-File vs Multi-Module
- Single-file: One
.tsfile compiles to one.dll - Multi-module: Entry point file with
import/exportcompiles all dependencies into a single.dll
Manual CLI Workflow
Basic Compilation
# Basic compilation
sharpts --compile Library.ts
# Custom output path
sharpts --compile Library.ts -o bin/Library.dll
# With IL verification (recommended for catching issues early)
sharpts --compile Library.ts --verify
Reference Assembly Mode
For compile-time C# references with IntelliSense support:
# Enable reference-assembly-compatible output
sharpts --compile Library.ts --ref-asm
# Full production build
sharpts --compile Library.ts --ref-asm --verify -o dist/Library.dll
CLI Options Reference
| Option | Description |
|---|---|
--compile / -c | Enable compilation mode |
-o <path> | Set output path (default: <input>.dll) |
--ref-asm | Emit reference-assembly-compatible output |
--sdk-path <path> | Explicit path to .NET SDK reference assemblies |
--verify | Verify emitted IL using Microsoft.ILVerification |
--preserveConstEnums | Keep const enum declarations in output |
--pack | Generate NuGet package after compilation |
--push <source> | Push package to NuGet feed |
--api-key <key> | API key for NuGet push |
--package-id <id> | Override package ID |
--version <ver> | Override package version |
NuGet Package Distribution
SharpTS can generate NuGet packages from compiled TypeScript libraries, making it easy to distribute TypeScript code for .NET consumption.
Package Metadata
Package metadata is read from package.json in the source directory:
{
"name": "my-typescript-lib",
"version": "1.0.0",
"description": "My TypeScript library for .NET",
"author": "Your Name",
"license": "MIT",
"keywords": ["typescript", "library"],
"repository": {
"url": "https://github.com/user/my-typescript-lib"
}
}
Creating Packages
# Basic package creation (uses package.json metadata)
sharpts --compile Library.ts --pack
# Output: Library.1.0.0.nupkg + Library.1.0.0.snupkg (symbols)
# Override version for pre-release
sharpts --compile Library.ts --pack --version 2.0.0-beta
# Custom package ID
sharpts --compile Library.ts --pack --package-id "MyCompany.Library"
Publishing to NuGet
# Push to nuget.org
sharpts --compile Library.ts --pack \
--push https://api.nuget.org/v3/index.json \
--api-key $NUGET_API_KEY
# Push to private feed
sharpts --compile Library.ts --pack \
--push https://pkgs.dev.azure.com/org/_packaging/feed/nuget/v3/index.json \
--api-key $AZURE_PAT
Package Contents
Generated packages include:
| Path | Content |
|---|---|
lib/net10.0/<name>.dll | Compiled assembly |
lib/net10.0/<name>.runtimeconfig.json | Runtime configuration |
README.md | Package readme (if present in source directory) |
CI/CD Integration
# GitHub Actions example
- name: Build and Publish
run: |
sharpts --compile src/Library.ts \
--pack \
--version ${{ github.ref_name }} \
--push https://api.nuget.org/v3/index.json \
--api-key ${{ secrets.NUGET_API_KEY }}
MSBuild Integration
Recommended: SharpTS.Sdk
The easiest way to integrate SharpTS into your build is using the MSBuild SDK:
<Project Sdk="SharpTS.Sdk/1.0.0">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<SharpTSEntryPoint>src/main.ts</SharpTSEntryPoint>
</PropertyGroup>
</Project>
This provides automatic compilation, tsconfig.json integration, and proper Clean support. See the MSBuild SDK Guide for full documentation.
Alternative: Manual Pre-Build Target
If you need more control or can't use the SDK, add a pre-build target to your .csproj:
<Target Name="CompileTypeScript" BeforeTargets="Build">
<Exec Command="sharpts --compile $(ProjectDir)TypeScript\Library.ts -o $(ProjectDir)CompiledTS\Library.dll" />
</Target>
Multiple TypeScript Files
<ItemGroup>
<TypeScriptFile Include="TypeScript\**\*.ts" />
</ItemGroup>
<Target Name="CompileTypeScript" BeforeTargets="Build"
Inputs="@(TypeScriptFile)"
Outputs="$(ProjectDir)CompiledTS\%(TypeScriptFile.Filename).dll">
<MakeDir Directories="$(ProjectDir)CompiledTS" />
<Exec Command="sharpts --compile %(TypeScriptFile.Identity) -o $(ProjectDir)CompiledTS\%(TypeScriptFile.Filename).dll" />
</Target>
Referencing Compiled DLLs
For reflection-based loading (copy to output):
<ItemGroup>
<None Include="CompiledTS\Library.dll" CopyToOutputDirectory="PreserveNewest" />
<None Include="CompiledTS\SharpTS.dll" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>
For compile-time reference (requires --ref-asm):
<ItemGroup>
<Reference Include="Library">
<HintPath>CompiledTS\Library.dll</HintPath>
</Reference>
</ItemGroup>
Complete .csproj Example
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<!-- Pre-build: Compile TypeScript -->
<Target Name="CompileTypeScript" BeforeTargets="Build">
<MakeDir Directories="$(ProjectDir)CompiledTS" />
<Exec Command="sharpts --compile $(ProjectDir)TypeScript\Library.ts -o $(ProjectDir)CompiledTS\Library.dll --ref-asm" />
</Target>
<!-- Copy compiled DLLs to output -->
<ItemGroup>
<None Include="CompiledTS\Library.dll"
CopyToOutputDirectory="PreserveNewest"
Condition="Exists('CompiledTS\Library.dll')" />
<None Include="CompiledTS\SharpTS.dll"
CopyToOutputDirectory="PreserveNewest"
Condition="Exists('CompiledTS\SharpTS.dll')" />
</ItemGroup>
</Project>
Consuming from C# - Reflection API
The reflection API works with standard compilation and provides maximum flexibility.
Loading the Assembly
using System.Reflection;
var assemblyPath = Path.Combine(AppContext.BaseDirectory, "Library.dll");
var assembly = Assembly.LoadFrom(assemblyPath);
Creating Instances
var personType = assembly.GetType("Person")!;
var person = Activator.CreateInstance(personType, "Alice", 30.0)!;
Property Access
Properties are emitted as real .NET properties with PascalCase names:
// Get property value
var nameProp = personType.GetProperty("Name")!;
string name = (string)nameProp.GetValue(person)!;
// Set property value
nameProp.SetValue(person, "Robert");
// Or access multiple properties
var ageProp = personType.GetProperty("Age")!;
double age = (double)ageProp.GetValue(person)!;
ageProp.SetValue(person, 31.0);
Note: TypeScript property names are converted to PascalCase (e.g., firstName becomes FirstName).
Method Invocation
Instance methods:
var greet = personType.GetMethod("greet")!;
string greeting = (string)greet.Invoke(person, null)!;
Static methods:
var calcType = assembly.GetType("Calculator")!;
var addMethod = calcType.GetMethod("add", BindingFlags.Public | BindingFlags.Static)!;
object sum = addMethod.Invoke(null, [10.0, 20.0])!;
Static Fields
var piField = calcType.GetField("PI", BindingFlags.Public | BindingFlags.Static)!;
double pi = (double)piField.GetValue(null)!;
Top-Level Functions
Top-level functions are compiled to static methods on the $Program class:
var programType = assembly.GetType("$Program")!;
var formatMessage = programType.GetMethod("formatMessage", BindingFlags.Public | BindingFlags.Static)!;
object formatted = formatMessage.Invoke(null, ["INFO", "Test message"])!;
Working with Inheritance
// Base class
var animalType = assembly.GetType("Animal")!;
var animal = Activator.CreateInstance(animalType, "Generic Animal")!;
// Derived class with overridden methods
var dogType = assembly.GetType("Dog")!;
var dog = Activator.CreateInstance(dogType, "Rex", "Golden Retriever")!;
var speak = dogType.GetMethod("speak")!;
string sound = (string)speak.Invoke(dog, null)!; // "Rex barks!"
Consuming from C# - Direct Reference
When compiled with --ref-asm, the DLL can be referenced at compile-time.
When to Use
- You need IntelliSense support in your IDE
- You want compile-time type checking
- You're building a tightly-coupled integration
How It Works
Standard compilation references System.Private.CoreLib (runtime-only). The --ref-asm flag rewrites references to SDK assemblies (System.Runtime, System.Collections, etc.), enabling compile-time usage.
Current Limitations
- Top-level functions are on
$Program(the$is valid in IL) - Some async patterns may have edge cases
Best Practices
Create Wrapper Classes
For cleaner APIs, wrap reflection calls:
public class PersonWrapper
{
private readonly object _instance;
private readonly Type _type;
private readonly PropertyInfo _nameProp;
private readonly PropertyInfo _ageProp;
private readonly MethodInfo _greet;
public PersonWrapper(Assembly assembly, string name, double age)
{
_type = assembly.GetType("Person")!;
_instance = Activator.CreateInstance(_type, name, age)!;
_nameProp = _type.GetProperty("Name")!;
_ageProp = _type.GetProperty("Age")!;
_greet = _type.GetMethod("greet")!;
}
public string Name
{
get => (string)_nameProp.GetValue(_instance)!;
set => _nameProp.SetValue(_instance, value);
}
public double Age
{
get => (double)_ageProp.GetValue(_instance)!;
set => _ageProp.SetValue(_instance, value);
}
public string Greet() => (string)_greet.Invoke(_instance, null)!;
}
Cache Reflection Metadata
private static readonly Dictionary<string, Type> _typeCache = new();
private static readonly Dictionary<(Type, string), MethodInfo> _methodCache = new();
public static Type GetCachedType(Assembly asm, string name)
{
if (!_typeCache.TryGetValue(name, out var type))
{
type = asm.GetType(name)!;
_typeCache[name] = type;
}
return type;
}
Handle Errors Properly
try
{
var assembly = Assembly.LoadFrom(dllPath);
var type = assembly.GetType("MyClass")
?? throw new TypeLoadException("MyClass not found");
}
catch (FileNotFoundException ex)
{
Console.WriteLine($"Assembly not found: {ex.FileName}");
}
catch (TargetInvocationException ex)
{
// TypeScript runtime error wrapped in TargetInvocationException
Console.WriteLine($"TypeScript error: {ex.InnerException?.Message}");
}
Working with Async Methods
var asyncMethod = type.GetMethod("fetchData")!;
var task = (Task<object>)asyncMethod.Invoke(instance, null)!;
var result = await task;
Build Automation
PowerShell Script
$ErrorActionPreference = "Stop"
# Compile TypeScript
$tsInput = "TypeScript\Library.ts"
$dllOutput = "CompiledTS\Library.dll"
Write-Host "Compiling TypeScript..."
sharpts --compile $tsInput -o $dllOutput --ref-asm --verify
# Build C# consumer
Write-Host "Building C# project..."
dotnet build
# Run
Write-Host "Running application..."
dotnet run
Bash Script
#!/bin/bash
set -e
echo "Compiling TypeScript..."
sharpts --compile TypeScript/Library.ts -o CompiledTS/Library.dll --ref-asm --verify
echo "Building C# project..."
dotnet build
echo "Running application..."
dotnet run
Troubleshooting
Assembly Load Failures
Error: FileNotFoundException: Could not load file or assembly
- Ensure
SharpTS.dllis in the same directory as the compiled TypeScript DLL - Check that
<name>.runtimeconfig.jsonexists alongside the DLL
Type Not Found
Error: GetType() returns null
- Classes are in the root namespace by default (no namespace prefix needed)
- If
@Namespace("X.Y")was used, include the namespace:assembly.GetType("X.Y.ClassName") - Top-level functions are on
$Programclass - Multi-module compilation uses qualified names:
$M_ModuleName_ClassName
Method/Property Not Found
Error: GetMethod() or GetProperty() returns null
- Properties use PascalCase names:
namebecomesName,firstNamebecomesFirstName - Check
BindingFlags.StaticvsBindingFlags.Instance - Include
BindingFlags.Publicfor public members
IL Verification Errors
Error: IL verification fails with --verify
- This indicates a compiler issue - please report with source code
- Compile without
--verifyas a workaround
Reference Assembly Errors
Error: System.Private.CoreLib not found when referencing DLL
- Use
--ref-asmflag during compilation - This rewrites assembly references to SDK assemblies
Parameter Type Mismatches
Error: ArgumentException when calling methods
- All
numberparameters expectdouble, notint - Pass
30.0instead of30 - Cast appropriately before invoking
Complete Example
TypeScript Source (Library.ts)
class Person {
name: string;
age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
greet(): string {
return "Hello, my name is " + this.name + " and I am " + this.age + " years old.";
}
haveBirthday(): void {
this.age = this.age + 1;
}
}
class Calculator {
static PI: number = 3.14159;
static add(a: number, b: number): number {
return a + b;
}
static multiply(a: number, b: number): number {
return a * b;
}
}
function formatMessage(prefix: string, message: string): string {
return "[" + prefix + "] " + message;
}
Compilation
sharpts --compile Library.ts -o CompiledTS/Library.dll --ref-asm --verify
C# Consumer (Program.cs)
using System.Reflection;
Console.WriteLine("=== SharpTS C# Interop Example ===");
// Load the compiled TypeScript assembly
var assemblyPath = Path.Combine(AppContext.BaseDirectory, "Library.dll");
var assembly = Assembly.LoadFrom(assemblyPath);
// 1. Create a Person instance
var personType = assembly.GetType("Person")!;
var person = Activator.CreateInstance(personType, "Alice", 30.0)!;
// 2. Access properties (using PascalCase names)
var nameProp = personType.GetProperty("Name")!;
var ageProp = personType.GetProperty("Age")!;
Console.WriteLine($"Person: {nameProp.GetValue(person)}, age {ageProp.GetValue(person)}");
// 3. Call instance method
var greet = personType.GetMethod("greet")!;
Console.WriteLine(greet.Invoke(person, null));
// 4. Call static method
var calcType = assembly.GetType("Calculator")!;
var add = calcType.GetMethod("add", BindingFlags.Public | BindingFlags.Static)!;
Console.WriteLine($"Calculator.add(10, 20) = {add.Invoke(null, [10.0, 20.0])}");
// 5. Access static field
var piField = calcType.GetField("PI", BindingFlags.Public | BindingFlags.Static)!;
Console.WriteLine($"Calculator.PI = {piField.GetValue(null)}");
// 6. Call top-level function
var programType = assembly.GetType("$Program")!;
var format = programType.GetMethod("formatMessage", BindingFlags.Public | BindingFlags.Static)!;
Console.WriteLine(format.Invoke(null, ["INFO", "Integration complete!"]));
Expected Output
=== SharpTS C# Interop Example ===
Person: Alice, age 30
Hello, my name is Alice and I am 30 years old.
Calculator.add(10, 20) = 30
Calculator.PI = 3.14159
[INFO] Integration complete!
See Also
- Using .NET Types from TypeScript - Use BCL and .NET libraries directly from TypeScript
- MSBuild SDK Guide - Integrate SharpTS into your .NET build process
- Execution Modes - Interpreted vs compiled mode details
- Code Samples - TypeScript to C# mappings
- Examples/Interop - Complete working example project