1 Anonymous Methods and the TypeContainer resolve order
2 -----------------------------------------------------
4 Anonymous methods add another resolving pass to the TypeContainer framework.
5 The new code works like this:
9 We can already determine whether or not a method contains anonymous
10 methods or iterators at parsing time, but we can't determine their
13 This means that at the end of the parsing stage, we already know
14 about all anonymous methods and iterators, but didn't resolve them
19 Anonymous method containers are not created until they are needed
20 which means we cannot use standard DefineType to setup type
23 Note: Even if block looks like anonymous method, it does not necessary
24 mean it's anonymous method, expression trees are good example.
28 At this point we enter anonymous methods land. We call Resolve on
29 method block which when hits anonymous method expression we start
30 anonymous method definition.
32 One of the hardest parts of the new anonymous methods implementation
33 was getting this resolve order right. It may sound complicated, but
34 there are reasons why it's done this way.
36 Let's have a look at a small example:
42 public void Hello<U> (U u)
44 public void Test<T> (T t)
56 After parsing this file, we already know that Test() contains an
57 anonymous method, but we don't know whether it needs to capture local
58 variable or access this pointer.
60 Because Test() is a generic method, it complicates things further
61 as we may need to create generic type container and transform all method
62 type parameters into class type parameters to keep method signature
63 compatible with requested delegate signature.
65 One key feature of the new code is using minimal possible anonymous
66 method overhead. Based on whether an anonymous method needs access to
67 this pointer or has access to local variables from outher scope new
68 TypeContainer (anonymous method storey) is created. We may end up with
71 1. No access to local variable or this
73 Anonymous method is emitted in a compiler generated static method
74 inside same class as current block.
76 2. No access to local variable but this is accessible
78 Anonymous method is emitted in a compiler generated instance method
79 inside same class as current block.
81 3. Local variable is accessible
83 New nested class (anonymous method storey) is created and anonymous
84 method block is emitted as an instance method inside this nested class.
86 Note: The important detail for cases 1 and 2 is that both methods are
87 created inside current block class, which means they can end up inside
88 anonymous method storey when parent scope needs access to local variable.
90 One important thing to keep in mind is that we neither know the type
91 of the anonymous methods nor any captured variables until resolving
92 `Test'. Note that a method's block isn't resolved until
93 TypeContainer.EmitCode(), so we can't call DefineMembers() on our
94 CompilerGeneratedClass'es until we emitted all methods.
96 Anonymous Methods and Scopes:
97 -----------------------------
99 The new code fundamentally changes the concept of CaptureContexts and
100 ScopeInfos. CaptureContext is completely gone together with ScopeInfo.
102 Unfortunately, computing the optimal "root scope" of an anonymous
103 method is very difficult and was the primary reason for the update in
104 late November 2006. Consider the following example:
107 TestDelegate d = null;
108 for (int i = 1; i <= 5; i++) {
110 TestDelegate temp = delegate {
111 Console.WriteLine ("i = {0}, k = {1}", i, k);
120 Note that we're instantiating the same anonymous method multiple times
121 inside a loop. The important thing is that each instantiation must
122 get the current version of `k'; ie. we must create a new instance 'k's
123 helper-class for each instantiation. They all share `i's helper-class.
125 This means that the anonymous method needs to be hosted in the inner
128 Because of that, we need to compute all the scopes before actually
129 creating the anonymous method.
131 Anonymous Methods and Generics:
132 -------------------------------
134 Creating and consuming generic types is very difficult and you have to
135 follow certain rules to do it right (the most important one is that
136 you may not use the class until it's fully created).
138 GMCS already has working code to do that - and one very important
139 policy in the new anonymous methods code is that it must not interfer
140 with GMCS's way of resolving and defining generic types; ie. everything
141 related to generics is handled during the normal TypeContainer
144 However, there is a problem when we are dealing with generics variables.
145 They may end up to be captured but their local generic type has been
146 already resolved. To handle this scenario type mutation was introduced,
147 to convert any method type parameter (MVAR) to generic type parameter
148 (VAR). This process is not straighforward due to way how S.R.E deals
149 with generics and we have to recontruct each reference of mutated
150 (MVAR->VAR) generic type.
153 The new `Variable' abstraction:
154 -------------------------------
156 There is a new `Variable' abstraction which is used for locals and
157 parameters; all the knowledge about how to access a variable and
158 whether it's captured or not is now in that new abstract `Variable'
159 class. The `LocalVariableReference' and `ParameterReference' now
160 share most of their code and have a common `VariableReference' base
161 class, which is also used by `This'.
163 `Variable' also controls whether or not we need to create a temporary
166 `Parameter' and `LocalInfo' both have a new ResolveVariable() method
167 which creates an instance of the new `Variable' class for each of
170 If we're captured, a `Field' has already been created for the variable
171 and since we're called during the normal TypeContainer resolve / emit
172 process, there' no additional "magic" required; it "just works".
174 CAUTION: Inside the anonymous method, the `Variable's type
175 determines the variable's actual type - outside it
176 is the ParameterReference / LocalVariableReference's
179 To make it more clear:
181 The type of a ParameterReference / LocalVariableReference
182 depends upon whether we're inside our outside the anonymous
183 method - and in case of generic, they are different !!!
185 The normal situation is that outside the anonymous method,
186 we may use the generic method parameters directly (ie.
187 MONO_TYPE_MVAR) - but inside the anonymous method, we're in
188 and generic class, not a generic method - so it's a generic
189 type parameter (MONO_TYPE_VAR).
191 There are several tests for this in my new test suite.
193 This does not only apply to variables; it's the same for types -
194 the same `T' may mean a completely different type depending upon
195 whether we're inside or outside the anonymous method: outside,
196 it's a generic method parameter (MONO_TYPE_MVAR) and inside, it's
197 a generic type parameter (MONO_TYPE_VAR) - so we already need to
198 handle this in the EmitContext to make SimpleNameResolve work.