Anonymous Methods and the TypeContainer resolve order ----------------------------------------------------- Anonymous methods add another resolving pass to the TypeContainer framework. The new code works like this: * Everything which may contain anonymous methods or iterators now implements the `IAnonymousHost' interface. This applies to `Method', `Constructor', `Accessor' and `Operator'. We can already determine whether or not a method contains anonymous methods or iterators at parsing time, but we can't determine their types yet. If we encounter an anonymous method or iterator while parsing, we add the information to the current `IAnonymousHost'. This means that at the end of the parsing stage, we already know about all anonymous methods and iterators, but didn't resolve them yet. * After parsing, RootContext.ResolveTree() calls DefineType() on all TypeContainers. * Inside TypeContainer.DefineType(), we do the following: - first we have to create our TypeBuilder via DefineTypeBuilder(). - after that, we scan all methods, constructors, operators and property/indexer accessors for anonymous methods and iterators. For each method which either contains anonymous methods or is implemented as iterator, we create a new helper class (the "root scope" of the anonymous method) and add it to the current type as a nested class. This is done by the new TypeContainer.ResolveMembers() method. - when done, we call DefineNestedTypes() to descend into our nested children. * RootContext.PopulateTypes() calls TypeContainer.ResolveType() and TypeContainer.DefineMembers() as usual and populates everything. * In TypeContainer.EmitType(), we call DefineMembers() and EmitType() on all our CompilerGeneratedClass'es once we're done emitting the current type. One of the hardest parts of the new anonymous methods implementation was getting this resolve order right. It may sound complicated, but there are reasons why it's done this way. Let's have a look at a small example: ===== delegate void Foo (); class X { public void Hello (U u) public void Test (T t) { T u = t; Hello (u); Foo foo = delegate { Hello (u); }; foo (); } } ===== After parsing this file, we already know that Test() contains an anonymous method, but we don't know its type until resolving it. Because Test() is a generic method, we need to create a generic helper class and then transform all method type parameters into class type parameters. One key feature of the new code is using the normal TypeContainer framework to create and use generic classes. For each method containing anonymous methods, we create one "root scope" which deals with generics and also hosts any captured parameter and `this'. In this example, this is done when calling DefineType() on `X's TypeContainer, during the ResolveMembers() pass. After that, we can handle the helper classes just like normal nested classes, so DefineNestedTypes() creates their TypeBuilders. One important thing to keep in mind is that we neither know the type of the anonymous methods nor any captured variables until resolving `Test'. Note that a method's block isn't resolved until TypeContainer.EmitCode(), so we can't call DefineMembers() on our CompilerGeneratedClass'es until we emitted all methods. Anonymous Methods and Scopes: ----------------------------- The new code fundamentally changes the concept of CaptureContexts and ScopeInfos. CaptureContext is completely gone while the ScopeInfo has been completely redesigned. Unfortunately, computing the "root scope" of an anonymous method is very difficult and was the primary reason for the update in late November 2006. Consider the following example: ==== TestDelegate d = null; for (int i = 1; i <= 5; i++) { int k = i; TestDelegate temp = delegate { Console.WriteLine ("i = {0}, k = {1}", i, k); sum_i += 1 << i; sum_k += 1 << k; }; temp (); d += temp; } ==== Note that we're instantiating the same anonymous method multiple times inside a loop. The important thing is that each instantiation must get the current version of `k'; ie. we must create a new instance 'k's helper-class for each instantiation. They all share `i's helper-class. This means that the anonymous method needs to be hosted in the inner helper-class. Because of that, we need to compute all the scopes before actually creating the anonymous method. Anonymous Methods and Generics: ------------------------------- Creating and consuming generic types is very difficult and you have to follow certain rules to do it right (the most important one is that you may not use the class until it's fully created). GMCS already has working code to do that - and one very important policy in the new anonymous methods code is that it must not interfer with GMCS's way of resolving and defining generic types; ie. everything related to generics is handled during the normal TypeContainer resolving process. When the anonymous methods code kicks in, all the generic types are already defined and ready for use. Adding a new non-generic class to such a generic type is really easy and not a problem - non-generic means that the new class does not introduce any new type parameters; it may still use its containing class'es type parameters: Example: class IAmGeneric { class IAmNot // must derive from System.Object { // using the containing classe's type parameter is ok. public T ButMayStillUseMyParentsT; } } The new `Variable' abstraction: ------------------------------- There is a new `Variable' abstraction which is used for locals and parameters; all the knowledge about how to access a variable and whether it's captured or not is now in that new abstract `Variable' class. The `LocalVariableReference' and `ParameterReference' now share most of their code and have a common `VariableReference' base class, which is also used by `This'. `Variable' also controls whether or not we need to create a temporary copy of a variable. Before emitting any method, we scan over all its parameters and local variables again and check whether any of them have been captured. `Parameter' and `LocalInfo' both have a new ResolveVariable() method which creates an instance of the new `Variable' class for each of them. If we're captured, a `Field' has already been created for the variable and since we're called during the normal TypeContainer resolve / emit process, there' no additional "magic" required; it "just works". CAUTION: Inside the anonymous method, the `Variable's type determines the variable's actual type - outside it is the ParameterReference / LocalVariableReference's type ! To make it more clear: The type of a ParameterReference / LocalVariableReference depends upon whether we're inside our outside the anonymous method - and in case of generic, they are different !!! The normal situation is that outside the anonymous method, we may use the generic method parameters directly (ie. MONO_TYPE_MVAR) - but inside the anonymous method, we're in and generic class, not a generic method - so it's a generic type parameter (MONO_TYPE_VAR). There are several tests for this in my new test suite. This does not only apply to variables; it's the same for types - the same `T' may mean a completely different type depending upon whether we're inside or outside the anonymous method: outside, it's a generic method parameter (MONO_TYPE_MVAR) and inside, it's a generic type parameter (MONO_TYPE_VAR) - so we already need to handle this in the EmitContext to make SimpleNameResolve work.