Quote of the Day

more Quotes

Categories

Get notified of new posts

Buy me coffee

  • Home>
  • C#>

Write generic codes with Delegate, Func, Action, and Anonymous Functions in C#

Published January 2, 2019 in .NET , .NET core , C# - 1 Comment

In this post, I talk about Delegate, Func, Action, anonymous  methods and lambda expressions in C# and how they help with making the codes more concise and generic.

Delegate

A delegate is a pointer to a function. It’s a C# specific feature.

The word “delegate” mean 

a person sent or authorized to represent others, in particular an elected representative sent to a conference.

Oxford Dictionary

Let’s consider instances of a web application running in a cluster of multiple servers and a load balancer. When a client sends a HTTP request to the application, the request first hits the load balancer, and the load balancer routes to a specific server within the cluster, based on some predetermined rules. In this example, a delegate is like the load balancer, and a function the server that performs the actual work.

Declaring a delegate

You can declare a delegate in two ways: using named and anonymous functions. Anonymous Method and Lambda Expressions are two types of Anonymous Functions.

Declaring a delegate using named methods

A delegate that represents a function must have the same signature as the function. You declare a delegate the same way you declare a function, with the addition of the keyword ‘delegate’. Let’s look at some examples.

// a delegate of a function that takes an int and returns
// an int
public delegate int DelegateOfFuncTakeIntReturnInt(int value); 
// a delegate of a function that takes an int and returns nothing 
public delegate void DelegateOfMethodTakeIntReturnNothing(int value); 
// a delegate of a generic function that takes an object of type D 
// and returns an object of type T.  
public delegate T DelegateOfMethodTakeTypeDReturnTypeT<T, D>(D param);

Once you declare a delegate, you can point it to any function that has the same signature as the delegate.


public int MethodTakeIntReturnInt(int value) 
{
    return value + 1; 
}

public int MethodTakeIntReturnInt2(int value) 
{
    return value - 1;
}
// The DelegateOfFuncTakeIntReturnInt has the same signature 
// as both the MethodTakeIntReturnInt and MethodTakeIntReturnInt2 
// functions and can point to any of them.  
DelegateOfFuncTakeIntReturnInt intFuncDelegate = MethodTakeIntReturnInt; 
Console.WriteLine(MethodTakeIntReturnInt(1) ; // output is 2; 
intFuncDelegate = MethodTakeIntReturnInt2; 
Console.WriteLine(intFuncDelegate(1)) // output is 0; 

Declaring a delegate using anonymous functions

From the documentation,

An anonymous function is an “inline” statement or expression that can be used wherever a delegate type is expected. You can use it to initialize a named delegate or pass it instead of a named delegate type as a method parameter.

Anonymous Functions (C# Programming Guide)

Two kinds of anonymous functions are anonymous methods and lambda expressions.

Anonymous Methods

An anonymous method is a type of anonymous functions.

An anonymous method that accepts one or more parameters and returns a value has this pattern:

var anonymousMethod = delegate(T para1, T1 parm2, T2 param3 ...) {
    // do some work ... 
    return TResult;  
}

An anonymous method that does not accept a parameter and returns a value has this pattern:

var anonymousMethod1 = delegate() {
    // do some work ... 
    return TResult; 
}

An anonymous method that does not accept a parameter and returns nothing has this pattern:

var anonymousMethod2 = delegate() {
    // do some work ...
}

Below shows examples of declaring and assigning anonymous methods to a delegate.

public delegate void MyDelegate(); 

MyDelegate anonymousDelegate  = delegate() 
{
   // do some work
};

public delegate string AnotherDelegate(string param); 

AnotherDelegate anotherAnonymousDelegate = delegate(T input) 
{
    return "Hello, " + input; 
};

Notice in the above snippets, I assign the anonymous methods into variables. You can pass an anonymous method directly as parameters to a method or assign it to variable, but you can’t just declare an anonymous method by itself.

One remark about anonymous methods from the documentation,

The scope of the parameters of an anonymous method is the anonymous-method-block.

Anonymous Methods (C# Programming Guide)

Essentially what that means is within the anonymous method, you can access the parameters of the method which encloses the anonymous method. This allows you to create dynamic methods that are adaptable to different delegates.

For example, suppose we have an API that serializes an exception object to JSON. We want to conditionally include or exclude certain information based on different criteria.

// declare the delegate
public delegate string ExceptionSerializer(Exception exception);

// using named methods to serialize the exception based on the 
// environment
public string ProdExceptionSerializer(Exception exception) 
{
   // in production environment, remove the stack trace. 
} 

public string DevExceptionSerializer(Exception exception)
{
    // in development environment, include the stack trace and other
    // information that can be helpful for debugging. 
}

In the above codes, we can get the environment variable in advance via dependency injection. As such, we don’t need to pass in other parameters besides the exception object. However, what if we need to base the serialization on some information that we don’t know in advance? For example, what if we want to base the serialization logic on the http request? If we create a method that does not match with the signature of the delegate, we will not be able to use the method in place of the delegate. However, we can create an anonymous method which has access to the parameters of the enclosing method.

public ExceptionSerializer MyExceptionSerializer(Exception exception, HttpRequest httpRequest) 
{
    return delegate(exception) {
        // serialize the exception based on whether the request is 
        // local
        if (httpRequest.isLocal())
        { 
            ....
        }
    };
}

As the above snippets demonstrate, you can access the parameters of the enclosing method from within an anonymous method to work with different delegates’ signatures.

Lambda Expressions and Statement Lambdas

A lambda expression is similar to an anonymous method in which you can assign it to a delegate of the matching signature or pass directly as argument to a method that accepts the delegate.

It is more concise to use a lambda expression instead of an anonymous method when you want to write inline codes for a delegate. That is because the structure is more compact, and in most cases, you don’t have to specify the types of the arguments as the compiler can infer the types.

In versions of C# before 2.0, the only way to declare a delegate was to use named methods. C# 2.0 introduced anonymous methods and in C# 3.0 and later, lambda expressions supersede anonymous methods as the preferred way to write inline code

Anonymous Methods (C# Programming Guide)
// Example of a lambda expression of which the inferred signature 
// matches with the ExceptionSerializer delegate
ExceptionSerializer exceptionSerializerExpression = exception => exception.toString(); 
  • In the expression exception => return exception.toString(), the compiler infers the types based on the signature of the ExceptionSerializer delegate. As such, the type of the argument exception is Exception, and the return type is string.
  • The => is the lambda operator.
  • You don’t need to include the “return” keyword if the expression returns a value.

The body of a lambda expression consists of a single line of codes. A statement lambda, in contrast, consists of multiple statements enclosed in brackets.

exceptionSerializerExpression = exception => {
                string result = "";
                if (environment.Equals("Prod"))
                {
                    // exclude the stacktrace when serializing ...
                }
                else if (environment.Equals("Dev"))
                {
                    // include more information for debugging ...
                }
                return result; 
            };

In the examples above, the lambda expression and the statement lambda only accept one parameter. If the expression or statement accepts no parameter or accepts more than one parameters, you separate the parameters with commas, and enclose them in parentheses.

// lambda expression for a delegate that accepts multiple parameters 
public delegate int NumberOperationDelegate(int value1, int value2) ;
NumberOperationDelegate multiplyOperation = (value1, value2)
 => value1 * value2; 

// lambda expression for a delegate that does not accept a parameter. 
public delegate void DoSomethingDelegate(); 
DoSomethingDelegate sayHello = () => Console.WriteLine("Hello"); 

If for some reasons, the compiler cannot infer the types, you can explicitly specify the types.

multiplyOperation = (int value1, int value2) => value1 * value2; 

Similar to an anonymous method, lambdas can access the parameters of the enclosing method or variables of the type that contains the lambda expression.

Lambdas can refer to outer variables (see Anonymous Methods) that are in scope in the method that defines the lambda function, or in scope in the type that contains the lambda expression. Variables that are captured in this manner are stored for use in the lambda expression even if the variables would otherwise go out of scope and be garbage collected. An outer variable must be definitely assigned before it can be consumed in a lambda expression

Lambda Expressions (C# Programming Guide)

 Using delegate to make your codes more generic.

Suppose you have a list of Item objects representing the merchandises you sale on your online store.

public class Item
{
     public double Price { get; set;}
     public bool IsOnSale { get; set; }
     public string Name { get; set }
}

You want the users to be able to search for the items that are on sale. A naive implementation to find those items may look like below.

 public List<Item> GetItemsOnSale(List<Item> allItems)
{
List<Item> onSaleItems = new List<Item>();
foreach (Item item in allItems)
{
if (item.IsOnSale)
{
onSaleItems.Add(item);
}
}
return onSaleItems;
}

Suppose you also want the users to search for items that are below a certain price. Below shows an example of a naive implementation.

public List<Item> GetItemsBelowPrice(List<Item> allItems, 
                                     double price)
{
    List<Item> itemsBelowGivenPrice = new List<Item>(); 
    foreach (Item item in allItems)
    {
        if (item.Price < price) 
        {
            itemsBelowGivenPrice.Add(item);
        }
    }
    return itemsBelowGivenPrice; 
 }

In the above methods, besides the conditional statements, the other codes are boilerplate codes and are basically the same in both methods. Let’s see how we can use delegates to simplify the codes and follow the DRY principle.

// declaring the delegate 
public delegate bool ShouldIncludeItemDelegate(Item item); 

// A named method to which the delegate can point.
public bool ItemOnSale(Item item) 
{
    return item.IsOnSale(); 
}
// assign the delegate using the named method. 
ShouldIncludeItemDelegate itemOnSaleFilter = this.ItemOnSale; 

// assign the delegate using an anonymous method 
ShouldIncludeItemDelegate itemBelowOneHundred = delegate(Item item) 
{
    return item.Price < 100; 
};
// dynamically create an anonymous method that filter an item based on 
// a given price. 
public ShouldIncludeItemDelegate ItemBelowCertainPrice(double price) 
{
    return delegate(Item item) 
    {
        return item.price < price; 
    }
} 

// using the delegate to reuse codes 
public List<Item> GetQualifyItems(List<Item> allItems, ShouldIncludeItemDelegate shouldIncludeItem)
{
    List<Item> qualifyItems = new List<Item>(); 
    foreach (Item item in allItems)
    {
        if (shouldIncludeItem(item))
        {
            qualifyItems.Add(item); 
        }
    }
    return qualifyItems; 
}

// use the named method as parameter to GetQualifyItems()
List<Item> qualifyItems = GetQualifyItems(this.allItems, itemOnSaleFilter); 
// use the anonymous method as parameter to GetQualifyItems()
qualifyItems = GetQualifyItems(this.allItems, itemBelowOnHundred); 
// dynamically create and use an anonymous method as parameter to 
// GetQualifyItems()
qualifyItems = GetQualifyItems(this.allItems, ShouldIncludeItemDelegate(200)); 

Func

Func is basically a specific type of delegate. A func is a delegate to a method that takes in zero or more arguments and returns a result. For example, consider the custom delegate we show in the section “Using delegate to make your codes more generic”, instead of declaring and using a custom delegate, we can use the build-in Func type Func<T, TResult> and passing Item as the type for T, and bool as the type for TResult.

// declaring the delegate 
 public delegate bool ShouldIncludeItemDelegate(Item item); 

// using the delegate 
 public List GetQualifyItems(List allItems, ShouldIncludeItemDelegate shouldIncludeItem) { ... }
// use the build-in Func type in place of the custom delegate 
public List GetQualifyItems(List allItems, Func<Item, bool> shouldIncludeItem) { ... }

Essentially, every custom delegate that points to a function that takes zero or more arguments and returns a result can be replaced with a built-in Func with the corresponding generic type arguments, of which all but the last represent the types of the actual function’s parameters, and the last represents the actual function’s return type.

In the examples above, I show examples of using delegate to filter out items in a list just for demonstration purpose. In actual projects, you’ll want to use the built-in libraries under the System.Linq namespace. For instance, instead of writing a custom method to get the items on sale out of the list of all items, I can just use the System.Linq.Enumerable.Where method which takes a Func<T,bool> where T is the generic type parameter of the items in the list.

// A Func for filtering out items that are not on sale. 
Func<Item, bool> onSaleItemsFunc = delegate(Item item)
{
    return item.isOnSale; 
}
List<Item> onSaleItems = allItems.Where(onSaleItemsFunc); 

The .NET framework has built in Func types that support 0 and up to 16 parameters.

Action

Action is another specific type of delegate which points to a method that accepts zero or more parameters and returns nothing (void).

// declaring a delegate to point to a method which does some work on 
// an Item. 
public delegate void ItemDelegate(Item item); 

public void PrintItemInfo(Item item)
{
    Console.WriteLine(item.Name); 
}

// generic method that iterates through a list of Item objects 
// and apply some work on each item. 
public void ApplyOnItem(List<Item> allItems, ItemDelegate itemDelegate) 
{
   foreach(Item item in allItems)
   {
       itemDelegate(item); 
   }
}

// print out the names of the items using delegate
ApplyOnItem(PrintItemInfo); 

In the above codes, instead of using the custom delegate, I could just use the built-in Action<T> delegate and pass in Item as the type parameter for T.

Action<Item> printItemAction = PrintItemInfo; 
// generic method that iterates through a list of Item objects 
// and apply some work on each item. 
public void ApplyOnItem(List<Item> allItems, Action<Item> itemAction) 
{
   foreach(Item item in allItems)
   {
       itemAction(item); 
   }
}
// print out the names of the items using printItemAction 
ApplyOnItem(printItemAction); 

The .NET framework has built-in Action types that support zero and up to 16 parameters.

Recap

In this post, we have looked at how to declare a custom delegate and point it to a named method or an anonymous function with matching signature. We look at .NET built-in delegate types: Func and Action. In addition, we also look at anonymous methods, expression lambdas and statement(s) lambdas as specific types of anonymous functions.

You can use delegate to make your codes more generic by abstracting out specific logic into named methods or anonymous functions which you can pass as arguments to other methods.

Additional resources

Delegates with Named vs. Anonymous Methods

how to work with action, functions and delegates in C#

Important uses of delegates and events

csharp function delegate from TutorialsTeacher

Action<T> delegate

Lambda Expressions (C# Programming Guide)

1 comment