Callbacks
Tsonic maps TypeScript function types to .NET delegate types. This guide covers how to use callbacks with Action<T> and Func<T, TResult>.
Overview
TypeScript arrow function types map to .NET delegates:
| TypeScript | C# Type | Description |
|---|---|---|
(x: T) => void |
Action<T> |
Callback with no return |
(x: T) => R |
Func<T, R> |
Callback with return value |
(x: T) => boolean |
Func<T, bool> |
Predicate callback |
Action Callbacks
Use Action<T> for callbacks that don't return a value.
Single Parameter
import { Console } from "@tsonic/dotnet/System.js";
import { List } from "@tsonic/dotnet/System.Collections.Generic.js";
import { int } from "@tsonic/core/types.js";
function forEach(items: List<int>, callback: (item: int) => void): void {
const len = items.Count;
for (let i: int = 0; i < len; i++) {
callback(items[i]);
}
}
export function main(): void {
const numbers = new List<int>();
numbers.Add(1);
numbers.Add(2);
numbers.Add(3);
forEach(numbers, (n: int) => {
Console.WriteLine(`Item: ${n}`);
});
}
Generated C#:
public static void forEach(List<int> items, Action<int> callback)
{
var len = items.Count;
for (var i = 0; i < len; i++)
{
callback(items[i]);
}
}
Multiple Parameters
import { int } from "@tsonic/core/types.js";
function forEachWithIndex(
items: List<int>,
callback: (item: int, index: int) => void
): void {
const len = items.Count;
for (let i: int = 0; i < len; i++) {
callback(items[i], i);
}
}
// Usage
forEachWithIndex(numbers, (item: int, index: int) => {
Console.WriteLine(`[${index}] = ${item}`);
});
Generated C#:
public static void forEachWithIndex(List<int> items, Action<int, int> callback)
Func Callbacks
Use Func<T, TResult> for callbacks that return a value.
Transform Functions
import { int } from "@tsonic/core/types.js";
function map(items: List<int>, transform: (item: int) => int): List<int> {
const result = new List<int>();
const len = items.Count;
for (let i: int = 0; i < len; i++) {
result.Add(transform(items[i]));
}
return result;
}
// Usage
const doubled = map(numbers, (n: int) => n * 2);
Generated C#:
public static List<int> map(List<int> items, Func<int, int> transform)
Predicate Functions
import { int } from "@tsonic/core/types.js";
function filter(
items: List<int>,
predicate: (item: int) => boolean
): List<int> {
const result = new List<int>();
const len = items.Count;
for (let i: int = 0; i < len; i++) {
const item = items[i];
if (predicate(item)) {
result.Add(item);
}
}
return result;
}
// Usage
const evens = filter(numbers, (n: int) => n % 2 === 0);
Generated C#:
public static List<int> filter(List<int> items, Func<int, bool> predicate)
Reducer Functions
import { int } from "@tsonic/core/types.js";
function reduce(
items: List<int>,
reducer: (acc: int, item: int) => int,
initial: int
): int {
let result = initial;
const len = items.Count;
for (let i: int = 0; i < len; i++) {
result = reducer(result, items[i]);
}
return result;
}
// Usage
const sum = reduce(numbers, (acc: int, n: int) => acc + n, 0);
Generated C#:
public static int reduce(List<int> items, Func<int, int, int> reducer, int initial)
Type Mappings
Action Variants
| TypeScript | C# |
|---|---|
() => void |
Action |
(a: A) => void |
Action<A> |
(a: A, b: B) => void |
Action<A, B> |
(a: A, b: B, c: C) => void |
Action<A, B, C> |
Func Variants
| TypeScript | C# |
|---|---|
() => R |
Func<R> |
(a: A) => R |
Func<A, R> |
(a: A, b: B) => R |
Func<A, B, R> |
(a: A, b: B, c: C) => R |
Func<A, B, C, R> |
Inline Lambdas
Pass arrow functions directly:
import { Console } from "@tsonic/dotnet/System.js";
import { List } from "@tsonic/dotnet/System.Collections.Generic.js";
import { int } from "@tsonic/core/types.js";
const numbers = new List<int>();
numbers.Add(1);
numbers.Add(2);
numbers.Add(3);
// Inline Action (List<T>.ForEach)
numbers.ForEach((n: int) => {
Console.WriteLine(`${n}`);
});
// Inline Func with return
const doubled = map(numbers, (n: int) => n * 2);
Higher-Order Functions
Returning Functions
import { int } from "@tsonic/core/types.js";
function createMultiplier(factor: int): (n: int) => int {
return (n: int) => n * factor;
}
const double = createMultiplier(2);
const triple = createMultiplier(3);
Console.WriteLine(`${double(5)}`); // 10
Console.WriteLine(`${triple(5)}`); // 15
Function Composition
import { int } from "@tsonic/core/types.js";
function compose(f: (x: int) => int, g: (x: int) => int): (x: int) => int {
return (x: int) => f(g(x));
}
const addOne = (x: int) => x + 1;
const double = (x: int) => x * 2;
const addThenDouble = compose(double, addOne);
Console.WriteLine(`${addThenDouble(5)}`); // 12
Async Callbacks
For async callbacks, use Promise return types:
import { Console } from "@tsonic/dotnet/System.js";
async function processAsync(callback: () => Promise<string>): Promise<void> {
const result = await callback();
Console.WriteLine(result);
}
await processAsync(async () => {
return "Async result";
});
Generated C#:
public static async Task processAsync(Func<Task<string>> callback)
{
var result = await callback();
Console.WriteLine(result);
}
Common Patterns
Event Handlers
type EventHandler = (sender: object, args: EventArgs) => void;
function addClickHandler(handler: EventHandler): void {
// ...
}
addClickHandler((sender: object, args: EventArgs) => {
Console.WriteLine("Clicked!");
});
Comparison Functions
import { int } from "@tsonic/core/types.js";
function sort(items: List<int>, compare: (a: int, b: int) => int): void {
// Use compare function for sorting
}
// Usage with comparison
sort(numbers, (a: int, b: int) => a - b);
Factory Functions
function createItem<T>(factory: () => T): T {
return factory();
}
const user = createItem(() => ({
name: "Alice",
age: 30,
}));
Using with LINQ
Callbacks work seamlessly with LINQ:
import { int } from "@tsonic/core/types.js";
import { List } from "@tsonic/dotnet/System.Collections.Generic.js";
import { Enumerable } from "@tsonic/dotnet/System.Linq.js";
const numbers = new List<int>();
numbers.Add(1);
numbers.Add(2);
numbers.Add(3);
numbers.Add(4);
numbers.Add(5);
// LINQ Where with predicate
const evens = Enumerable.Where(numbers, (n: int) => n % 2 === 0);
// LINQ Select with transform
const doubled = Enumerable.Select(numbers, (n: int) => n * 2);
// LINQ Aggregate with reducer
const sum = Enumerable.Aggregate(
numbers,
0,
(acc: int, n: int) => acc + n
);
Tips
Type Annotations in Lambdas
Always include type annotations in lambda parameters for clarity:
// Good - explicit types
forEach(items, (item: int) => { ... });
// Avoid - implicit types may cause issues
forEach(items, item => { ... });
Returning from Lambdas
For single-expression returns, use concise syntax:
// Concise return
const doubled = map(numbers, (n: int) => n * 2);
// Block return
const processed = map(numbers, (n: int) => {
const result = n * 2;
return result;
});
See Also
- Type System - Function type mappings
- .NET Interop - Working with .NET APIs
- Async Patterns - Async callbacks