WindowsDevCenter.com
oreilly.comSafari Books Online.Conferences.

advertisement


AddThis Social Bookmark Button

Understanding the Nuances of Delegates in C#
Pages: 1, 2

We Can Pass a Delegate Variable Around as an Argument

Once we have a variable declared as a delegate, we can pass that delegate around as if it were a variable with an appropriate type. The sample code doesn't have an instance of this usage, so let me put up a small example:




namespace SomeNameSpace
{
  public static somefunction(SampleDelegate d)
  {
    d("log this message);
  }
}

Related Reading

C# in a Nutshell
By Peter Drayton, Ben Albahari, Ted Neward

The beauty of a delegate is very obvious here. One would have thought in a doubt-filled haste that while defining a function that takes a delegate argument, we would have to specify the input and output parameters of that delegate. But that aspect is nicely abstracted out into the delegate declaration already.

Let Us Call the Delegate

We have defined a delegate, instantiated it, and passed it around, but how do we call it? This is where a delegate works like a function. Let us borrow a few lines from the sample code:


// Calling a simple delegate
bool returnCode
  = m_sampleDelegate("A simple delegate call");

As we know, the local variable m_sampleDelegate is loaded with a function called log. This log function, when invoked, will write to console the passed-in text message. Because log is bound to m_sampleDelegate, you can use m_sampleDelegate as if it were a log function. Following that thought, we should see the log method invoked and the text written to console when the above sample code is executed. The return code from the log method will be returned as the output of the delegate call.

You Can Call Two Delegates With One Call: Combining Delegates

Here is where, in my mind, delegates truly break out of the mold and touch a new level of abstraction. Multiple delegates can be composed into one. The composed delegate can be used as if it is a single function call. Let us lead with an example borrowed from the sample code:


// Combining delegates
SampleDelegate d1 = new SampleDelegate(log);
SampleDelegate d2 = new SampleDelegate(log);
SampleDelegate compositeDelegate = 
  (SampleDelegate)Delegate.Combine(d1, d2);

where d1 and d2 are two delegates happens to be pointing to the same log function. Using the static method on the Delegate class, we can create a new delegate called compositeDelegate (bearing the same signature) by combining d1 and d2. Now when we call the composite delegate, we will will see that the log method is called twice and the output written twice to the console. Here is the method invocation for the composite delegate:


returnCode = 
  compositeDelegate("Sample composite delegate");

Calling the composite delegate is the same as calling any other delegate. The immediate question, then, is: can one combine these delegates dynamically at run time? I will leave that to the reader to investigate. Nevertheless, I will report on a shortcut for combining delegates based on overloading. Here are a couple of lines borrowed from the sample:

  compositeDelegate = d1 + d2;
returnCode = 
  compositeDelegate("Sample composite delegate, " + 
                    "using the + operator");

Where d1 and d2 are the delegates that were previously defined and shown. Again, calling the composite delegate is no different. I will leave it to the reader again to see if other overloaded operators exist.

You Can Remove Delegates From a Composite Delegate

It is only deductive that one should look for the possibility of removing delegates from a composite delegate. The following code does exactly that. But, unlike the case of composition, the removal syntax raises an important doubt. Let us remember to examine that doubt.

  
SampleDelegate resultingDelegate = 
     (SampleDelegate)Delegate.Remove(compositeDelegate,d2);
compositeDelegate("Sample composite delegate after " + 
                  "removing one of them");
resultingDelegate("Resulting Sample composite delegate " +
                  "after removing one of them");

It is not difficult to wrongly conclude that after the removal, the composite delegate will have only one delegate left in its belly. Just to verify that, we have used the compositeDelegate in a subsequent call to see what it prints. For the record, it prints the message twice, while the resultingDelegate prints it only once. This experiment gives us another important rule of thumb as far as delegates go: delegates are immutable. You can compose new ones out of them or par them down into new ones, but original ones remain casted (pun nevertheless, but unintended).

Retrieving the Target Class From a Delegate

As we have been covering so far, a delegate points to a function. And this function can be either a static function or an instance function belonging to an object of a class. It is possible to retrieve this target at run time if needed. For example, you can retrieve a target from a delegate, cast the target to an appropriate class, and call the additional methods on that object. If the delegate points to a static method, then the target points to null. Let us examine the code that exercises this functionality:

  
if (m_sampleDelegate.Target == null)
{
  log("This delegate is pointing to a static method");
}

SampleDelegateDriver sdd = 
             (SampleDelegateDriver)m_sampleDelegate.Target;

Console.WriteLine("TOSTR:" + 
                  m_sampleDelegate.Target.ToString());

The first part of this sample tests to see if a target exists. If the target does not exist, the implication is that the delegate is pointing to a static method. The second part obtains the target of the m_sampleDelegate, which is the object SampleDelegateDriver itself. And we can call the toString method on that object to prove that it is actually pointing to that object.

If A Delegate Was a Class, What Other Methods Does It Have?

If a delegate was a function, should it be surprising to us that we were able to do m_sampleDelegate.Target? We know functions don't have attributes; again, this is possible because it is a class. As a result, it has more methods than we normally use a delegate for. In fact, some of them we have already seen (Combine() and Remove), but they happen to be class-level static methods. An instance method of a delegate that is useful is GetInvocationList(). This method allows us to iterate through the individual delegates of a composite delegate. The following code demonstrates this:

  
compositeDelegate = d1 + d2;
int i=0;
foreach(Delegate x in compositeDelegate.GetInvocationList())
{
  log("delegate " + i + x.Method.ToString());
  i++;
}

A Few Notes on Delegate Composition

When we combine delegates, it is clear that they all have to have the same signature. When a composite delegate is called, the return value of the last delegate is returned as the return value of the composite. The intermediate return values are ignored. Also, when one of the delegates throws an exception, the processing is discontinued and the exception returned to the caller. This is not untypical of the pipeline processing that is the cornerstone of delegates. Again I will have to leave it to the reader to investigate if there are any options that one can set on a delegate to indicate to ignore exceptions and continue. If a removal operation on a composite delegate removes the last delegate, then the caller will get back null.

Conclusion

Delegates are routinely used in the publish/subscribe programming model in .NET. Delegates are also used in asynchronous programming models in .NET. Both subjects are fairly large, and I won't be able to cover them here. I encourage the reader to look at them as a followup for the material presented here.

References

I have largely depended on the documentation that comes with VisualStudio.NET. I have used the following keywords: "Delegate class c#". You may be able to locate this information on MSDN online as well.

Satya Komatineni is the CTO at Indent, Inc. and the author of Aspire, an open source web development RAD tool for J2EE/XML.


Return to ONDotnet.com