Delegates are ubiquitous throughout .NET development though many developers are not aware of their full range. This stems from the fact that few books or articles do a good job tying together all ways in which delegates may be used. Additionally there are often misconceptions over delegate semantics - how to construct a delegate within a given context. This post seeks to clarify the muddy water around delegates by use of demonstrative tests. Each test is explicitly named for the semantic it demonstrates and internally each test asserts expected behavior. As the title implies, this post is not intended as an introduction to delegates. If you are looking for such material Juval Lowy does a decent job here.
There are numerous delegates defined within the Framework Class Libraries (FCL). Before you define a custom delegate - see if an existing delegate suits your need. The following test highlights several commonly used delegates:
[Test]
public void NumerousConcreteDerivativesOfDelegateExistInFrameworkClassLibraries()
{
Assert.IsInstanceOfType(typeof (Delegate), (EventHandler) delegate(object sender, EventArgs e) { ; });
Assert.IsInstanceOfType(typeof (Delegate), (ThreadStart) delegate { ; });
Assert.IsInstanceOfType(typeof (Delegate), (ParameterizedThreadStart) delegate(object parameters) { ; });
Assert.IsInstanceOfType(typeof (Delegate), (EventHandler<EventArgs>) delegate(object sender, EventArgs e) { ; });
Assert.IsInstanceOfType(typeof (Delegate), (Action<string>) delegate(string target) { ; });
Assert.IsInstanceOfType(typeof (Delegate), (Predicate<string>) delegate(string target) { return true; });
}
It is clear from the test that each of these delegates derive from System.Delegate. In fact every delegate in .NET derives from System.Delegate. This fact is often either unknown or forgotten. Keep delegate hierarchy in mind and you will be able to work through any question about delegates. For example, did you know you can store a reference to an anonymous method in a Delegate object? This strategy is useful when you need to pass an anonymous method in many places - or when you wish to return a delegate.
Delegates can be used in their raw form to provide publish/subscribe services. There are a few drawbacks to using raw delegates in this manner, so .NET defines an event keyword to work around these drawbacks. The following test demonstrates that an event itself derives from System.Delegate.
private event EventHandler itsEvent;
// unrelated members elided
[Test]
public void EventIsInstanceOfDelegate()
{
Assert.IsInstanceOfType(typeof (Delegate), itsEvent);
}
Here is a test that demonstrates how to use raw delegates for eventing. The method starts by storing a delegate object - in this case an EventHandler delegate. A subscriber registers for event notification and then the event is raised. If you want to know more about the limitations of using raw delegates for eventing - I highly recommend picking up a copy of Programming .NET Components.
[Test]
public void DelegateObjectsContainAnInvocationListAndMayBeUsedForEventing()
{
EventHandler delegateEvent = delegate { ; };
using (ManualResetEvent signal = new ManualResetEvent(false))
{
delegateEvent += delegate { signal.Set(); };
Assert.IsNotEmpty(delegateEvent.GetInvocationList());
delegateEvent(this, null);
Assert.IsTrue(signal.WaitOne(1, false));
}
}
A common misconception is that you can pass anonymous delegates as arguments to System.Delegate parameters. As long as you remember that System.Delegate is abstract you will soon free yourself from this misconception. The issue is that delegate inference (compiler inferring an intended type) cannot create an instance of System.Delegate as it is abstract. You must provide the compiler a derived type with which it can complete the inference. The following unit test demonstrates a compiler error ("Cannot convert anonymous method...") as well as a common hint to delegate inference. In the latter case the anonymous delegate is cast as EventHandler.
[Test]
public void TypeInferenceCannotConvertAnonymousMethodToInstanceOfDelegate()
{
CompilerResults compilerResults = new AssemblyFactory().Create(string.Format(theMethod, "Delegate instance = delegate { ; };"));
Assert.AreEqual("Cannot convert anonymous method block to type 'System.Delegate' because it is not a delegate type",
compilerResults.Errors[0].ErrorText);
compilerResults = new AssemblyFactory().Create(string.Format(theMethod, "Delegate instance = (EventHandler) delegate { ; };"));
Assert.IsEmpty(compilerResults.Errors);
}
Type inference generates a method or an entire type depending on the context. In the former case it is not uncommon for ambiguities to occur that stop inference dead in its tracks. No worries, the solution is simple. Just specialize the anonymous method enough so that the compiler is able to unambiguously infer the method you are targeting.
[Test]
public void TypeInferenceCannotConvertAnonymousMethodToAmbiguousDelegate()
{
CompilerResults compilerResults = new AssemblyFactory().Create(string.Format(theMethod, "Thread thread = new Thread(delegate { ; });"));
Assert.AreEqual("The call is ambiguous between the following methods or properties: " +
"'System.Threading.Thread.Thread(System.Threading.ThreadStart)' and " +
"'System.Threading.Thread.Thread(System.Threading.ParameterizedThreadStart)'", compilerResults.Errors[0].ErrorText);
compilerResults = new AssemblyFactory().Create(string.Format(theMethod, "Thread thread = new Thread(delegate() { ; });"));
Assert.IsEmpty(compilerResults.Errors);
}
Notice that the second anonymous delegate includes an empty parameter list. This is enough to eliminate ambiguity. It is clear that you cannot create a ParameterizedThreadStart delegate from that signature and that what you want is the ThreadStart delegate.
You may have noticed earlier that an anonymous delegate used a locally scoped variable (delegate { signal.Set(); }). The lifetime of this delegate has zero connection to the lifetime of its defining method. In fact it is totally possible that the defining method goes out of scope before the delegate is invoked. But if stack-based variables scoped to the defining method are used after the method goes away, how can the delegate object continue to access those variables? Does this cause runtime exceptions? Compiler exceptions?
[Test]
public void DelegateObjectShouldCaptureLocalVariableNotTheValue()
{
int total = 0;
for (int index = 0; index < 5; ++index)
ThreadPool.QueueUserWorkItem(delegate { total += index; });
Thread.Sleep(100);
Assert.AreNotEqual(0 + 1 + 2 + 3 + 4, total);
}
In the method above the variable index is captured by lexical closure into the delegate. As worker threads execute their delegates the index is incremented - thus at least one of the values between 0 and 4 are skipped. The point of this test is to demonstrate that a closure occurs around variables - not values. Keep this in mind because you may normally expect a variable to go out of scope - yet it is being maintained while the closure exists.
In the next example a closure exists around anotherIndex but not index. Thus the iteration completes deterministically.
[Test]
public void DelegateObjectShouldCaptureEachInstanceOfLocalVariable()
{
int total = 0;
for (int index = 0; index < 5; ++index)
{
int anotherIndex = index;
ThreadPool.QueueUserWorkItem(delegate { total += anotherIndex; });
}
Thread.Sleep(100);
Assert.AreEqual(0 + 1 + 2 + 3 + 4, total);
}
Delegates have a definite place in your toolbox. In fact any asynchronous programming in .NET will force you to use delegates. Knowing what makes them tick will help you most in deciding when and how to use them.
| Attachment | Size |
|---|---|
| delegates.zip | 1.26 MB |
Comments
I get it!
So I must admit that the terms Closure and lexical context threw me off at first. But when I replaced them with Scope and Stack (and happened to be using something like this in a unit test I was writing) it coalesced for me.
My example was this:
[Test] public SomeWorkRelatedUnitTestIWasWorkingOnThatTestsThreadSafetyOfACertainCall() { // Some Mocking initialization goes here //.... List autos = new List(); for (int i = 0; i < 1; i++) { autos.Add(new AutoResetEvent(false)); ThreadPool.QueueUserWorkItem( delegate { myWorker.InitializeProcessors(true, mySettings); autos[i].Set(); }); } WaitHandle.WaitAll(autos.ToArray()); myMocks.VerifyAllExpectationsHaveBeenMet(); }When I ran my test I was getting an exception at the autos[i].Set() call. at this point i was 1, when I expected it to be 0. I remember reading this post this morning and hopped on back to see what I was missing.
Rjae's words (once I understood them) were exactly what was happening to me. i was continuing to increment while the work was being done, because the delegate has a view into the scope that i lives at.
When I switched to something like:
[Test] public SomeWorkRelatedUnitTestIWasWorkingOnThatTestsThreadSafetyOfACertainCall() { List autos = new List(); for (int i = 0; i < 1; i++) { int anotherI = i; autos.Add(new AutoResetEvent(false)); ThreadPool.QueueUserWorkItem( delegate { myWorker.InitializeProcessors(true, mySettings); autos[anotherI].Set(); }); } WaitHandle.WaitAll(autos.ToArray()); }It really became clear to me what Rjae was saying. It's almost like a delegate gets it's own "local(ish)" storage.
Thanks Rjae for the timely post!
sorry for the formatting of the code.
There were spaces there when I hit post :P
I hope you don't mind, but I replaced <code> with <pre class="code cs"> to retain your formatting.
Sweet! Thanks!
You sure do get it Jay. But I kinda already knew that.
I am thrilled that the post was so timely and helped.