Index
- Basics of anonymous methods
- Static data member usage with anonymous methods
- Instance data member usage with anonymous methods
- Local variable usage with anonymous methods
- Scoping and local variable usage with anonymous methods
- Local variable usage with anonymous methods within loop control structures
- Summary
1. Basics of anonymous methods
Anonymous methods is a new language feature in C# 2.0. The focus of this article is to provide the readers with a better understanding of the internal implementation and working of anonymous methods. This article is not intended to be a complete language feature reference for anonymous methods.
Anonymous methods allow us to define a code block where a delegate object is acceptable. This facility saves us an extra step of creating a delegate for small code blocks that we want to pass to a delegate. It also removes the cluttering of small methods in the class code. Let us say, for instance, that we have a string
collection class named MyCollection
. This class has a method that retrieves all the items in the collection that satisfies a user supplied criteria, i.e., the caller decides whether a particular item in the collection qualifies for being retrieved as part of the return array from this method.
public class MyCollection
{
public delegate bool SelectItem(string sItem);
public string[] GetFilteredItemArray(SelectItem itemFilter)
{
List<string> sList = new List<string>();
foreach(string sItem in m_sList)
{
if (itemFilter(sItem) == true) sList.Add(sItem);
}
return sList.ToArray();
}
public List<string> ItemList
{
get
{
return m_sList;
}
}
private List<string> m_sList = new List<string>();
}
We can write code as shown below to use the above defined class:
public class Program
{
public static void Main(string[] args)
{
MyCollection objMyCol = new MyCollection();
objMyCol.ItemList.Add("Aditya");
objMyCol.ItemList.Add("Tanu");
objMyCol.ItemList.Add("Manoj");
objMyCol.ItemList.Add("Ahan");
objMyCol.ItemList.Add("Hasi");
string[] AStrings = objMyCol.GetFilteredItemArray(FilterStringWithA);
Console.WriteLine("----- Strings starting with letter 'A' -----");
foreach(string s in AStrings)
{
Console.WriteLine(s);
}
string[] TStrings = objMyCol.GetFilteredItemArray(FilterStringWithT);
Console.WriteLine("----- Strings starting with letter 'T' -----");
foreach(string s in TStrings)
{
Console.WriteLine(s);
}
}
public static bool FilterStringWithA(string sItem)
{
if (sItem[0] == 'A')
return true;
else
return false;
}
public static bool FilterStringWithT(string sItem)
{
if (sItem[0] == 'T')
return true;
else
return false;
}
}
One can see that for each simple criteria we want to provide, we should define a method (static or instance). This soon clutters the class code. With anonymous methods, the code becomes much natural. Below is the class Program
re-written using anonymous methods.
public class Program
{
public delegate void MyDelegate();
public static void Main(string[] args)
{
MyCollection objMyCol = new MyCollection();
objMyCol.ItemList.Add("Aditya");
objMyCol.ItemList.Add("Tanu");
objMyCol.ItemList.Add("Manoj");
objMyCol.ItemList.Add("Ahan");
objMyCol.ItemList.Add("Hasi");
string[] AStrings = objMyCol.GetFilteredItemArray(delegate(string sItem)
{
if (sItem[0] == 'A')
return true;
else
return false;
});
Console.WriteLine("----- Strings starting with letter 'A' -----");
foreach (string s in AStrings)
{
Console.WriteLine(s);
}
string[] TStrings = objMyCol.GetFilteredItemArray(delegate(string sItem)
{
if (sItem[0] == 'T')
return true;
else
return false;
});
Console.WriteLine("----- Strings starting with letter 'T' -----");
foreach (string s in TStrings)
{
Console.WriteLine(s);
}
}
}
As shown in the above sample, we were able to define the criteria with in-line code blocks instead of defining a new method to represent each criteria. Truly speaking, using such inline code may look natural and avoid defining new methods, but if this technique is used for larger inline code blocks, then the code soon becomes unmanageable and may lead to duplications. So, be selective in using methods vs. inline anonymous methods as delegates/event handlers.
Now, this is the basics of anonymous methods. The rest of the article deals with how anonymous methods work internally in different scenarios. Understanding how anonymous methods are implemented and work internally is important for their correct usage. Else, the results of your code using anonymous methods will look unpredictable.
2. Static data member usage with anonymous methods
Anonymous methods always start with a delegate
keyword, followed by parameters to be used inside the method and the method body itself. As seen from the above code sample, users need not specify the return type of the anonymous method. It is inferred from the return
statement within the method body. The .NET CLR cannot execute free flowing code blocks like anonymous methods. CLR requires that every method it executes is part of a type and should be a static method or instance method. So when you write anonymous methods in a class code and compile the code, the C# compiler silently creates a static or instance method within the same class where you defined the anonymous method. So anonymous methods are just a convenient syntax for defining your own methods inside the class to pass to delegates (delegate handlers/event handlers).
When you compile the above sample, the C# compiler creates two private static methods inside the class 'Program
', where we defined the anonymous methods. It then replaces the anonymous methods with the address of those static methods. How the compiler decides to create static methods or instance methods depends on the usage of the static or instance data members of the class within which the anonymous methods are defined. In our sample, we are not using any data member of the class 'Program
', so the C# compiler creates a static method to encapsulate the code of our anonymous methods as it would be efficient to invoke a static method over an instance method. Below is the ILDASM view of the 'Program
' class for the sample assembly. The highlighted portions show the new static methods that are added silently by the C# compiler to the 'Program
' class.
If we had used any static data of the 'Program
' class within the anonymous methods, the C# compiler would still create a static method within the 'Program
' class to wrap the anonymous method.
3. Instance data member usage with anonymous methods
Let us define a new instance method in the 'Program
' class of our sample that uses one of its instance data members. The code below shows the modifications to the sample:
public class Program
{
public delegate void MyDelegate();
public static void Main(string[] args)
{
Program p = new Program();
for(int i=1;i<=5;i++)
p.TestInstanceDataMembers();
}
public void TestInstanceDataMembers()
{
MyDelegate d = delegate
{
Console.WriteLine("Count: {0}",++m_iCount);
};
d();
}
public int m_iCount = 0;
}
We defined a new instance method, TestInstanceDataMembers
, in the 'Program
' class that defines an anonymous method, which uses the instance data member, m_iCount
, of the 'Program
' class. When the sample is compiled, the C# compiler will create a private instance method to wrap the anonymous method defined within TestInstanceDataMembers
. The C# compiler has to create an instance method because the method needs access to the instance data member of the 'Program
' class. Below is the ILDASM view of the 'Program
' class for the sample assembly. The selection in the image below shows the new private instance method that is added silently by the C# compiler to the 'Program
' class.
4. Local variable usage with anonymous methods
By now, we have a basic understanding of how anonymous methods work and are internally implemented. Ultimately, C# creates private methods to wrap anonymous methods. And these method signatures match that of the delegate that they are being assigned to. Now, let's take a look at the code below:
public class Program
{
public delegate void MyDelegate();
public static void Main(string[] args)
{
int iTemp = 100;
MyDelegate dlg = delegate
{
Console.WriteLine(iTemp);
};
dlg();
}
}
According to what we have learned about anonymous method till now, this code should not compile. Because we are not using any instance data members, the C# compiler should create a private static method in the 'Program
' class to wrap this anonymous method. But how will the new method access the local variable? This leads us to believe that this code will not compile. But surprisingly, the C# compiler compiles this code without any errors or warnings. Also, when you execute this sample, the output prints the correct value of the variable iTemp
on the console screen.
Let us enter the advanced world of anonymous methods. An anonymous method has the capability of encapsulating the values of the surrounding variables that are used inside its method body. This encapsulation applies to all the variables that are local to the method in which the anonymous method is defined. When the C# compiler identifies the usage of a local variable within the body of an anonymous method, it does the following:
- Create a new private class that is an inner class of the class where the anonymous method is defined.
- Create a public data member within the new class, with the same type and name as the local variable that is used in the anonymous method body.
- Create a public instance method within the new class that wraps the anonymous method.
- Replace the declaration of the local variable with the declaration of the new class. Create an instance of this new class in place of the declaration of the local variable.
- Replace the use of the local variable within the anonymous method body and outside the anonymous method, with the data member of the new class instance.
- Replace the anonymous method definition with the address of the instance method defined in the new class.
So during compilation, the code above will be transformed by the C# compiler as shown below:
public class Program
{
private class InnerClass
{
private void InstanceMethod()
{
Console.WriteLine(iTemp);
}
public int iTemp;
}
public delegate void MyDelegate();
public static void Main(string[] args)
{
InnerClass localObject = new InnerClass();
localObject.iTemp = 100;
MyDelegate dlg = new MyDelegate(localObject.InstanceMethod);
dlg();
}
}
As shown in the above pseudo-code, the C# compiler generates a private inner class for the 'Program
' class. The local variable used in the anonymous method is captured as an instance data member in the newly created inner class. And the anonymous method itself is wrapped in the instance method of the inner class. Finally, the instance method is used as a delegate handler in the Main
method. This way, when the delegate is invoked, it will have the correct value for the local variable that is encapsulated in its anonymous method. The selection in the image below shows the new private inner class that is added silently by the C# compiler to the 'Program
' class.
The local variables that are used in the anonymous methods have life outside the regular method in which they are used. This technique, in other languages, is popularly known as closures. Apart from the simple syntax convenience that anonymous methods provide, closures is one powerful technique that anonymous methods provide a developer. This technique allows the delegate handler code (anonymous method) access to the local variables within the regular method scope in which it is being defined. This allows out-of-band data, data other than the delegate parameters, to be passed on to the delegate, which can be used during its method execution. Without this technique, each delegate and its corresponding handler method has to declare parameters that represent the local contextual data, which may become un-manageable over time.
5. Scoping and local variable usage with anonymous methods
We discussed about the implementation of anonymous methods within the main scope of a method. When an anonymous method is defined within a nested scope, and individual scope level local variables are used inside the anonymous method, C# creates a private inner class for each scope. For instance, let scope 1 has local variable iTemp
, and scope 2, which is the nested scope of scope 1, has a local variable jTemp
. Let us define an anonymous method within scope 2 that uses local variables iTemp
and jTemp
from scope 1 and scope 2. The code below shows the sample as described above:
public class Program
{
public delegate void MyDelegate();
public static void Main(string[] args)
{
MyDelegate dlg = null;
int iTemp = 100;
if (iTemp > 50)
{
int jTemp = 200;
dlg = delegate
{
Console.WriteLine("iTemp: {0}, jTemp: {1}",iTemp,jTemp);
};
}
dlg();
}
}
When the above code is compiled, the C# compiler creates two inner classes within the 'Program
' class. One inner class wraps the local variable iTemp
as a public data member. The second inner class wraps the local variable in the nested scope, jTemp
, as a public data member, and wraps the anonymous method within the same nested scope as the public instance method. Below is the pseudo-code generated by the C# compiler for the above code:
public class Program
{
private class InnerClassScope1
{
public int iTemp;
}
private class InnerClassScope2
{
public void InstanceMethod()
{
Console.WriteLine("iTemp: {0}, jTemp: {1}",
localObjectScope1.iTemp, jTemp);
}
public InnerClassScope1 localObjectScope1;
public int jTemp;
}
public delegate void MyDelegate();
public static void Main(string[] args)
{
MyDelegate dlg = null;
InnerClassScope1 localObject1 = new InnerClassScope1();
localObject1.iTemp = 100;
if (localObject1.iTemp > 50)
{
InnerClassScope2 localObject2 = new InnerClassScope2();
localObject2.localObjectScope1 = localObject1;
localObject2.jTemp = 200;
dlg = new MyDelegate(localObject2.InstanceMethod);
}
dlg();
}
}
As shown in the above code, the inner class that wraps the anonymous method will have objects for all the inner classes that represent the outer scope local variables, which are used in its anonymous method, as public data members. The below image shows the ILDASM view of the inner classes that are silently created by the C# compiler:
6. Local variable usage with anonymous methods within loop control structures
The encapsulation of local variables into class data members has an interesting but dangerous side effect when dealing with loop control structures. Let us take a look at the code shown below:
public class Program
{
public delegate void MyDelegate();
public static void Main(string[] args)
{
MyDelegate d = null;
for (int i = 1; i <= 5; i++)
{
MyDelegate tempD = delegate
{
Console.WriteLine(i);
};
d += tempD;
}
d();
}
}
What will be the output when the above code is run? Our intention is to capture the loop counter variable 'i
' in our anonymous method and display it. Our expected output should be as shown below:
1
2
3
4
5
But if you run the above code, the output will be as follows:
6
6
6
6
6
If we carefully recall our knowledge of the inner workings of anonymous methods, I mentioned that any local variable captured within the anonymous method would be replaced by an instance data member of a newly created inner class for that scope. For the loop control variable, the scope is the scope containing the for
loop, which is the main method body shown in the above sample code. So when this code is compiled, the C# compiler generates the code that creates an instance of the inner class, wrapping the anonymous method and the loop counter variable, outside the for
loop. And the data member of this inner class instance, representing the loop counter variable, will be used in-place of the original loop counter variable used for looping and also within the anonymous method. So the data member from the same instance of the inner class is used for looping and also within the instance method that wraps the anonymous method. As a result when the loop is completed, the instance data member would have incremented six times. A very important note here is that, though the loop ends after five iterations, the loop counter variable is incremented six times by the time it comes out of the loop control structure. Since this loop control variable is an instance data member, the sixth increment that triggers the loop termination condition is preserved within the loop counter variable. Since a method of the same instance is used as a delegate handler for the anonymous method, when finally the delegate is invoked, all the instances of the delegate will be pointing to the same instance, and will display the same value for the data member, which is 6. This is the dangerous side effect that I mentioned in the beginning of this section.
To overcome this issue and achieve the expected results, the anonymous method should capture a local variable within the scope of the for
loop, which will have the same value of the loop counter variable. This can be achieved by changing the sample code as shown below:
public class Program
{
public delegate void MyDelegate();
public static void Main(string[] args)
{
MyDelegate d = null;
for (int i = 1; i <= 5; i++)
{
int k = i;
MyDelegate tempD = delegate
{
Console.WriteLine(k);
};
d += tempD;
}
d();
}
}
When you run the above code sample, the output would be as expected, i.e.:
1
2
3
4
5
The reason is that, the C# compiler would create the instance of the inner class wrapping the local variable 'k
' for each iteration of the for
loop. And the method wrapping the anonymous method on this per loop iteration instance is used as a delegate handler.
7. Summary
Anonymous methods are a very useful and powerful addition to C# 2.0 language. Apart from introducing some syntactic sugar to the delegate declaration and usage, Microsoft has gone to great lengths in making the anonymous method code mix naturally into the containing method body, including access to the local variables within the scope of the containing method definition.
Finally, I hope that this article provides C# developers with insight required to utilize anonymous methods correctly and intelligently.