Click here to Skip to main content
15,867,686 members
Articles / Programming Languages / C# 5.0

You Had Me at Delegate

Rate me:
Please Sign up or sign in to vote.
4.67/5 (17 votes)
4 Jul 2015CPOL6 min read 16.2K   24   7
The power of delegates outside of events.

Introduction

I could never really find a good tutorial on delegates... Somehow, the discussion always tends to switch over to a talk about events a quarter way through. Delegates themselves can be a powerful tool that can be used to both increase class abstraction and for dynamic method invocation. Before we get to the code, let's first try to create a working definition of what a delegate is.

Delegate: (Let's list a few of the things that come to mind when thinking of delegates.)

  1. A Type (i.e. StringBuilder, DBContext, List)
  2. A method pointer which is type safe (delegate instances store references to methods)
  3. Looks similar to a method declaration
  4. The signature of a delegate, however, includes the return type, unlike a method signature which consists of only the method name along with the type and number of parameters.
  5. Allows methods to be treated as, for the most part, as any other C# object: allowing them to be assigned to other variables and passed as parameters.

So we have: A delegate is a special type (class) that defines a method signature whose instance can be used to reference another method that follows its definition. (This definition will become clearer through examples). To define a delegate, it's actually quite simple. We just add the word "delegate" with the return type to a typical method signature.

C#
public delegate void ProcessInventoryHandler( String text );

The above code defines a delegate that can reference a method that has one parameter of type String and returns void. The following methods can both be referenced using the above delegate:

C#
void AddToInventory( String text )
void FooBar( String foo )

As you can see, the method and the parameter name(s) are not important--just the signature. To think of this in another way, imagine if your boss gives you the company's corporate seal, and requests that you act on her behalf at a contract signing with a new client. It doesn't matter that your boss is a 6 foot 5 inch tall woman that's more than twice your weight and has a deeper voice than you do, the important thing here is that both of you have the same signature (corporate seal). The code definition of delegate is a bit deceiving however, as it looks more like a C/C++ function declaration than a class definition. The CLR does the heavy lifting behind the scenes to convert each delegate to a proper class definition. We can verify this by looking at the MSIL (Microsoft Intermediate Language) code that is generated from the above delegate:

C#
.class public auto ansi sealed MyDotNet.ProcessInventoryHandler
	extends [mscorlib]System.MulticastDelegate
{
	// Methods
	.method public hidebysig specialname rtspecialname 
		instance void .ctor (
			object 'object',
			native int 'method'
		) runtime managed 
	{
	} // end of method ProcessInventoryHandler::.ctor

	.method public hidebysig newslot virtual 
		instance void Invoke (
			string text
		) runtime managed 
	{
	} // end of method ProcessInventoryHandler::Invoke

	.method public hidebysig newslot virtual 
		instance class [mscorlib]System.IAsyncResult BeginInvoke (
			string text,
			class [mscorlib]System.AsyncCallback callback,
			object 'object'
		) runtime managed 
	{
	} // end of method ProcessInventoryHandler::BeginInvoke

	.method public hidebysig newslot virtual 
		instance void EndInvoke (
			class [mscorlib]System.IAsyncResult result
		) runtime managed 
	{
	} // end of method ProcessInventoryHandler::EndInvoke

} // end of class MyDotNet.ProcessInventoryHandler

Although the details of the MSIL are a bit beyond the scope of this tutorial, we can see here that the ProcessInventoryHandler delegate is a public sealed class that inherits from another class called "MulticastDelegate". Unlike a typical class however, an instance of the delegate class is referred to as a "Delegate" not as an "Object." To create a delegate instance, we must first pass the reference of the method we would like to link to the delegate. This is a lot easier than it sounds. As we know, in C#, the most common way to call a method is by using the form: MethodName (int x);, where "int x" represents any number of parameters from zero to 2^16 - 1 [mileage may vary, MSIL limits number of args that can be loaded to an unsigned of int16]. Here the runtime knows that we are making a call to a method because of the open and closing parenthesis. However if we were to write just the method's name, without the parenthesis, or parameters for that matter, we can obtain the needed method reference to pass to the delegate. This method reference is called a Method Group in C#, as a single method name can, but does not necessarily have to, refer to several different method name to method-signature combinations. The CLR will match the delegate type to the appropriate method overload. This matching by the CLR however, does bring us to a few important asides:

  1. Method Groups have no meaning outside the world of delegates. (As of C# 6)
  2. The CLR does not like ambiguity when it comes to delegate to method assignments.

To get a better understanding of what I mean by ambiguity in regards to delegate assignments, let's take a look at the following few code samples:

C#
static void Main()
{	
	var dollarValue = ShowMeTheMoney(new Dollar());
	Console.WriteLine ("The dollar amount: {0}{1}", dollarValue.Symbol, dollarValue.Amount);
	
	var wonValue = ShowMeTheMoney(new Won());
	Console.WriteLine ("The won amount: {0}{1}", wonValue.Symbol, wonValue.Amount);
}

static Dollar ShowMeTheMoney(Dollar d) 
{ 		
	d.Amount = 5m;
	return d; 
}

static Won ShowMeTheMoney(Won w) 
{ 		
	w.Amount = 5000m;
	return w; 
}

//The currency class, has just one property for the amount
//for the purpose of this example... It will have two derived
//classes (currencies) Dollar and Won
public class Currency { public decimal Amount {get; set;} }


public class Dollar : Currency 
{ 
	private readonly string _symbol = "$";
	public string Symbol { get { return _symbol; } }
}

public class Won : Currency 
{
	private readonly string _symbol = "W";
	public string Symbol { get { return _symbol; } }
}

The above code compiles and runs fine. The output is of course:

C#
The dollar amount: $5
The won amount: W5000

Let's now see what happens when we add an additional layer of abstraction to our simple program above, through the use of delegates. For this, we just need to add a delegate declaration and modify the main method as follows (class definitions were left out for brevity):

C#
delegate Currency MoneyHandler(Currency c);

static void Main()
{		
	MoneyHandler theBank = ShowMeTheMoney;
		
	var dollarValue = theBank(new Dollar());
	Console.WriteLine ("The dollar amount: {0}{1}", dollarValue.Symbol, dollarValue.Amount);
		
	var wonValue = theBank(new Won());
	Console.WriteLine ("The won amount: {0}{1}", wonValue.Symbol, wonValue.Amount);
	
}

static Dollar ShowMeTheMoney(Dollar d) 
{ 		
	d.Amount = 5m;
	return d; 
}

static Won ShowMeTheMoney(Won w) 
{ 		
	w.Amount = 5000m;
	return w; 
}

If we try to compile the above code, the compiler will go BANG [Do not pass go, do not collect $200] and give you the friendly "No overload for 'Method Group' matches delegate 'Delegate Name' " error message. We might try to call foul play and say to ourselves, but wait, both Dollar and Won are derived from Currency, this should work. However, in there lies the problem, that "both" should work. This is just one of the half-dozen-or-so reasons why when the CLR has to match a Method Group to a delegate type, covariance--having a more derived type--for the method parameters is not allowed. However, an important distinction must be made here between the two concepts of ambiguity and multiplicity. In order words, when the method signature is properly defined and distinct, a one-to-many relationship is valid.

C#
delegate Currency MoneyHandler(Currency c);

static void Main()
{		
	MoneyHandler theBank; 
	
	//We are allowed to chain methods to a single delegate instance
	//by using the += operator [x = x + y]
	theBank = ShowMeTheMoneyDollar;
	theBank += ShowMeTheMoneyWon;
		
	//We make just one call to the delegate instance, but both methods
	//will be called. Note, only the return value of the last method 
	//called will be set to 'myCurrency'. 
	var myCurrency = theBank(new Currency());		
}

static Dollar ShowMeTheMoneyDollar(Currency c) 
{ 		
	c = new Dollar();
	c.Amount = 5m;
	
	Console.WriteLine ("The dollar amount: ${0}", c.Amount);
	
	return c as Dollar; 
}

static Won ShowMeTheMoneyWon(Currency c) 
{ 		
	c = new Won();
	c.Amount = 5000m;
	
	Console.WriteLine ("The won amount: W{0}", c.Amount);
	
	return c as Won; 
}

The output for the above code is the exact same as we had in the simpler non delegate case above, simply:

C#
The dollar amount: $5
The won amount: W5000

The observant eye might also notice something else about the above code, the return types of both methods are derived types of the delegate's return type. In other words, Dollar and Won both inherit from Currency. But wait a minute, didn't I just mention a few paragraphs up on why that was bad? Again, before you cry foul, let's take a look at what's really happening along with a few of the subtle differences in having covariance in the return type verus in the parameters.

  1. Probably the easiest difference to spot is that now we have two distinct Method Groups, so no ambiguity.
  2. Return types are by default are "out"s and parameters are by default "in"s. An out type by design is immutable--even though we are returning a more baser type, data integrity is maintained. There is actually no loss in precision as the object never changes, only the reference type. "A rose by any other name..." Well, to be a bit more precise, we are just calling a Rose a Flower.

Well, don't take my word for it, let's add a few lines of code to our main:

C#
delegate Currency MoneyHandler(Currency c);

static void Main()
{		
	MoneyHandler theBank; 
	
	//We are allowed to chain methods to a single delegate instance
	//by using the += operator [x = x + y]
	theBank = ShowMeTheMoneyDollar;
	theBank += ShowMeTheMoneyWon;
		
	//We make just one call to the delegate instance, but both methods
	//will be called. Note, only the return value of the last method 
	//called will be set to 'myCurrency'. 
	var myCurrency = theBank(new Currency());
	
	
	Console.WriteLine (((Won)myCurrency).Symbol);		// W
	Console.WriteLine (myCurrency.GetType()); 		// Won
	Console.WriteLine (((Dollar)myCurrency).Symbol); 	//Error: Unable to cast object of 
                                                         	//type 'Won' to type 'Dollar'.
}

[*Note: The compiler would go BANG if we tried to do this with value types, such as double and float: there is no reference to pass].

The Object class contains a "GetType" method that returns an object's type. Since all object instances are derived from Object, we can call the GetType method on 'myCurrency' and see that it's in fact the derived, 'Won'. If we try to cast 'myCurrency' to 'Dollar', for example, an error is thrown. Now let's get to our final topic for delegates, and that is, besides events, so why should we use them? The best way to answer that might possibly be by using the following, albeit over simplified, analogy: class is to interface as method is to...delegate. In-fact, many delegate-method use cases can be replaced with an equivalent interface-class implementation. But to do so would create wordy code, decrease readability, and opens you up to the potential for unwanted "side-effects."

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
QuestionProblems with delegates Pin
BotReject12-Jul-15 8:26
BotReject12-Jul-15 8:26 
GeneralAn interesting dig into delegates, multicast delegates and parameter matching. Pin
OneWinsto11-Jul-15 10:04
OneWinsto11-Jul-15 10:04 
GeneralRe: An interesting dig into delegates, multicast delegates and parameter matching. Pin
Dominic D Williams11-Jul-15 10:35
professionalDominic D Williams11-Jul-15 10:35 
QuestionPossibly a typo? Pin
doright6-Jul-15 8:33
doright6-Jul-15 8:33 
Looks similar to a method deceleration

Item #3 in your list says deceleration. Could this be typo for declaration?
The early bird gets the worm, but the second mouse gets the cheese.

AnswerRe: Possibly a typo? Pin
Dominic D Williams6-Jul-15 9:26
professionalDominic D Williams6-Jul-15 9:26 
QuestionArticle promises more than it delivers Pin
Emile van Gerwen6-Jul-15 3:02
Emile van Gerwen6-Jul-15 3:02 
AnswerRe: Article promises more than it delivers Pin
Dominic D Williams6-Jul-15 7:35
professionalDominic D Williams6-Jul-15 7:35 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.