Skip to content

Delegate conversion and nominal delegates

DelegateDeclaration = "delegate" "func" TypeName "(" [ ParameterList ] ")" [ ReturnType ] .
DelegateConversion = FunctionName | LambdaExpression .

A method group or lambda converts to a delegate only where its target delegate type is known: a typed binding, parameter, return, or event. An untyped let f = name is ill-formed because E# will not choose implicitly between a heap/multicast delegate and an allocation-free function pointer.

func double(n: int) -> int = n * 2
let f: Func<int, int> = double

Method-group conversion binds directly to the declared method; no forwarding method is synthesized. A lambda uses the target delegate’s Invoke signature to type its parameters. delegate func Name(...) declares a nominal sealed MulticastDelegate subtype. It is distinct from a structurally identical Func/Action, but accepts target-typed methods and lambdas just like a BCL delegate.

Func<R> represents a zero-argument delegate returning R; Func<A, R> through the higher generic arities represent arguments followed by a return type. Action and Action<A> through their higher arities represent void-returning delegates. They are ordinary imported CLR delegate types, not E# keywords, so their nominal identity is exactly the BCL identity a C# API expects.

func parse(text: string) -> int = Int32.Parse(text)
func log(value: int) { Console.WriteLine(value) }
let parser: Func<string, int> = parse
let sink: Action<int> = log
let add: Func<int, int, int> = (a, b) => a + b

Target typing flows into lambdas: a and b above are int, and the lambda’s result must be compatible with the final Func type argument. A named function or lambda can convert independently to two different delegate types with compatible Invoke signatures, but the resulting values are still nominally distinct. For example, Predicate<int> and Func<int, bool> accept the same isEven method group yet cannot be assigned to one another after construction.

Delegates are reference values. A non-capturing method group has a direct method target; a capturing lambda has a compiler-generated closure object as its target and shares captured mutable locals with sibling closures. Use &(…) / &name instead when a non-capturing, single-target function pointer is required; a function pointer neither allocates a delegate nor supports multicast subscription.

-> Func<…> is an ordinary function result type. It means the function returns a delegate value; it does not invoke that value before returning. Return position is a delegate target, so a method group can convert there directly.

func double(value: int) -> int = value * 2
func getDoubler() -> Func<int, int> = double
func useDoubler() -> int {
let doubler = getDoubler()
return doubler(21)
}

The returned delegate above targets double itself. It is still a Func<int, int> object, with the usual CLR delegate identity and lifetime, even though its method needs no captured state.

A function literal may itself declare a delegate result. This is the closure-factory form: the outer literal receives values, and each call returns a delegate that may retain them.

let makeOffset: Func<int, Func<int, int>> =
func(offset: int) -> Func<int, int> {
return func(value: int) -> int = value + offset
}
let addTwo = makeOffset(2)
let answer = addTwo(40) // 42

Each call of makeOffset creates a distinct capture environment. Copies of one returned delegate share that environment; delegates returned by separate calls do not. The environment remains live for as long as a returned delegate can reach it. Thus a returned closure may safely retain a var, and invoking it observes the same mutable storage on later calls.

func makeCounter() -> Func<int> {
var count = 0
return func() -> int {
count += 1
return count
}
}
let next = makeCounter()
let first = next() // 1
let second = next() // 2

This is normal closure lifetime, not task ownership: returning a delegate does not start work or introduce an implicit join. The delegate runs only when it is invoked. The closure-capture rules, including the task func mutable-capture restriction, are defined in Function values.