Understanding C# Features (7) Higher-Order Function
[LINQ via C#] - [C# Features]
Function as input/output
Higher-order function is a function taking one or more function parameters as input, or returning a function as output. The other functions are called first-order functions. (Again, in C#, the term function and the term method are identical.) C# supports higher-order function from the beginning, since a C# function can use almost anything as its input/output, except:
- Static types, like System.Convert, System.Math, etc., because there cannot be a value (instance) of a static type.
- Special types in .NET framework, like System.Void.
A first-order function can take some data value as input and output:
public class DataType { } public static DataType FirstOrder(DataType dataValue) { return dataValue; } public static void CallFirstOrder() { DataType inputValue = default(DataType); DataType outputValue = FirstOrder(inputValue); }
To get a higher-order function, just replace above DataType/dataValue with a function type/function value. In C#, delegate type can be viewed as function type, and delegate instance can be viewed as function value (instance). So:
public delegate void FunctionType(); public static FunctionType HigherOrder(FunctionType functionValue) { return functionValue; } public static void CallHigherOrder() { FunctionType inputValue = default(FunctionType); FunctionType outputValue = HigherOrder(inputValue); }
Above HigherOrder becomes a higher-order function takes function as input and output.
Besides named function, anonymous first-order/higher-order functions can be easily expressed with lambda expression:
public static partial class HigherOrderFunction { public static void Lambda() { Action firstOrder1 = () => { }; Action<Action> higherOrder1 = action => action(); Func<int> firstOrder2 = () => default(int); Func<Func<int>> higherOrder2 = () => firstOrder2; } }
Higher-order functions are everywhere in .NET framework, like fore mentioned Sort method of List<T>. It’s signature is:
namespace System.Collections.Generic { public class List<T> { public void Sort(Comparison<T> comparison); } }
Its comparison parameter is a function value of Comparison<T> function type:
namespace System { public delegate int Comparison<in T>(T x, T y); }
Most LINQ query methods are higher-order functions, like Where. Its signature is:
public static IEnumerable<TSource> Where<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate);
Its predicate parameter is a function value of function type Func<TSource, bool>:
public static partial class LinqToObjects { public static IEnumerable<int> Positive(IEnumerable<int> source) { return source.Where(value => value > 0); } }
First-class function
So far C# has been demonstrated to have first class functions. C# function can be compared to C# object side by side:
Data (object) | Function (method) | |
Type | Object type: class | Function type: delegate type |
Value | Object: class instance | Function value: delegate instance |
Assignment | Can be assigned to variable | Can be assigned to variable |
Storage | Can be stored in data structure | Can be stored in data structure |
Input | Can be function’s parameter | Can be higher-order function’s parameter |
Output | Can be function’s return value | Can be higher-order function’s return value |
Nesting | Can be nested (e.g. Exception.InnerException) | Can be nested (function in function): anonymous function, lambda expression, closure with non-local variable access |
Equality | Reference equality testable | Reference equality testable |
They can have type and instance:
public static partial class FirstClass { public class ObjectType { public ObjectType InnerObject { get; set; } } public delegate void FunctionType(); public static void ObjectInstance() { ObjectType objectValue = new ObjectType(); } public static void FunctionInstance() { FunctionType functionValue1 = FunctionInstance; // Named function. FunctionType functionValue2 = () => { }; // Anonymous function. } }
They can be stored in data structure:
public static partial class FirstClass { public static ObjectType objectField = new ObjectType(); public static FunctionType functionField1 = FunctionInstance; // Named function. public static FunctionType functionField2 = () => { }; // Anonymous function. }
They can be function parameter and return value:
public static partial class FirstClass { public static ObjectType InputOutputObject(ObjectType objectValue) => objectValue; public static FunctionType InputOutputFunction(FunctionType functionValue) => functionValue; }
They can be nested:
public static partial class FirstClass { public static void NestedObject() { ObjectType outerObject = new ObjectType() { InnerObject = new ObjectType() }; } public static void NestedFunction() { object nonLocalVariable = new object(); FunctionType outerFunction = () => { object outerLocalVariable = nonLocalVariable; FunctionType innerFunction = () => { object innerLocalVariable = nonLocalVariable; }; }; } }
They are reference equality testable:
public static partial class FirstClass { public static void ObjectEquality() { ObjectType objectValue1; ObjectType objectValue2; objectValue1 = objectValue2 = new ObjectType(); bool areEqual1 = objectValue1 == objectValue2; // true. ObjectType objectValue3 = null; bool areEqual2 = objectValue2 == objectValue3; // false. } public static void FunctionEquality() { FunctionType functionValue1; FunctionType functionValue2; functionValue1 = functionValue2 = () => { }; bool areEqual1 = functionValue1 == functionValue2; // true. FunctionType functionValue3 = null; bool areEqual2 = functionValue2 == functionValue3; // false. } }
Apparently, C# treats functions as first-class citizen, just like C# objects.