Delegate conversion and nominal delegates
Grammar
Section titled “Grammar”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 * 2let f: Func<int, int> = doubleMethod-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<>, Action<>, and BCL delegates
Section titled “Func<>, Action<>, and BCL delegates”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> = parselet sink: Action<int> = loglet add: Func<int, int, int> = (a, b) => a + bTarget 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.
Returning delegates and closure factories
Section titled “Returning delegates and closure factories”-> 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 * 2func 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) // 42Each 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() // 1let second = next() // 2This 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.