Tsonic GitHub
Edit on GitHub

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