Ejemplo n.º 1
0
        private void Transform(ModuleDefinition mod, MethodDefinition method, MethodDefinition newMethod, FieldDefinition expressionField, out TypeDefinition closureType)
        {
            var writer = newMethod.Body.GetILProcessor();
            Dictionary <ParameterReference, VariableDefinition> oldParameterToNNewVariable = new Dictionary <ParameterReference, VariableDefinition>();

            newMethod.Body.InitLocals = true;
            writer.Emit(OpCodes.Nop);
            var allParameters = method.Parameters.ToList();

            if (method.Body.ThisParameter != null)
            {
                allParameters.Insert(0, method.Body.ThisParameter);
            }

            foreach (var p in allParameters)
            {
                var variable = new VariableDefinition(mod.ImportReference(ParameterExpression));
                newMethod.Body.Variables.Add(variable);
                writer.Emit(OpCodes.Ldtoken, p.ParameterType);
                writer.Emit(OpCodes.Call, mod.ImportReference(this.Type_GetTypeFromHandle));
                writer.Emit(OpCodes.Ldstr, p == method.Body.ThisParameter ? "this" : p.Name);
                writer.Emit(OpCodes.Call, mod.ImportReference(this.Expression_Parameter));
                writer.Emit(OpCodes.Stloc, variable.Index);
                oldParameterToNNewVariable.Add(p, variable);
            }

            method.Body.SimplifyMacros();
            var reader = new ILReader(method.Body);
            //var c = new <>__DisplayClass
            bool isDup = false;
            Dictionary <MetadataToken, ParameterReference> captureFieldToParameter = new Dictionary <MetadataToken, ParameterReference>();

            closureType = null;
            if (reader.Is(OpCodes.Newobj))
            {
                var newObj = reader.Get(OpCodes.Newobj);
                closureType = ((MethodDefinition)newObj.Operand).DeclaringType;
                if (reader.TryGet(OpCodes.Stloc) != null) //General case
                {
                    //c.a = a;
                    while (LookaheadClosureAssignment(isDup, ref reader, out var oldParameter, out var fieldReference))
                    {
                        captureFieldToParameter.Add(fieldReference.MetadataToken, oldParameter);
                    }
                }
                else if (reader.TryGet(OpCodes.Dup) != null) //only found for 1 parameter in release
                {
                    isDup = true;
                    if (!LookaheadClosureAssignment(isDup, ref reader, out var oldParameter, out var fieldReference))
                    {
                        throw new InvalidOperationException($"Not expected (in {method.FullName})");
                    }

                    captureFieldToParameter.Add(fieldReference.MetadataToken, oldParameter);
                }
            }

            Dictionary <VariableDefinition, VariableDefinition> rebasedVariables = new Dictionary <VariableDefinition, VariableDefinition>();

            foreach (var v in method.Body.Variables)
            {
                if (closureType != null && v.VariableType == closureType)
                {
                }
                else
                {
                    var newVariable = new VariableDefinition(mod.ImportReference(v.VariableType));
                    rebasedVariables.Add(v, newVariable);
                    newMethod.Body.Variables.Add(newVariable);
                }
            }

            while (reader.HasMore())
            {
                if (LookaheadExpressionFieldConstant(isDup, ref reader, out var token))
                {
                    var param = captureFieldToParameter[token.Value];
                    writer.Emit(OpCodes.Ldloc, oldParameterToNNewVariable[param].Index);
                }
                else if (!method.IsStatic && LookaheadExpressionThisConstant(ref reader, method.DeclaringType))
                {
                    writer.Emit(OpCodes.Ldloc, oldParameterToNNewVariable[method.Body.ThisParameter].Index);
                }
                else if (LookaheadArray(ref reader))
                {
                    if (reader.HasMore())
                    {
                        throw new InvalidOperationException($"The method {method.FullName} should only call As.Expression with an expression tree lambda");
                    }

                    if (oldParameterToNNewVariable.Count == 0)
                    {
                        writer.Emit(OpCodes.Call, mod.ImportReference(new GenericInstanceMethod(this.Array_Empty)
                        {
                            GenericArguments = { this.ParameterExpression }
                        }));
                    }
                    else
                    {
                        writer.Emit(OpCodes.Ldc_I4, oldParameterToNNewVariable.Count);
                        writer.Emit(OpCodes.Newarr, mod.ImportReference(this.ParameterExpression));
                        for (int i = 0; i < oldParameterToNNewVariable.Count; i++)
                        {
                            writer.Emit(OpCodes.Dup);
                            writer.Emit(OpCodes.Ldc_I4, i);
                            writer.Emit(OpCodes.Ldloc, i);
                            writer.Emit(OpCodes.Stelem_Ref);
                        }
                    }

                    var funcType = ((GenericInstanceType)expressionField.FieldType).GenericArguments.Single();
                    writer.Emit(OpCodes.Call, mod.ImportReference(new GenericInstanceMethod(this.Expression_Lambda)
                    {
                        GenericArguments = { funcType }
                    }));
                    writer.Emit(OpCodes.Stsfld, expressionField);
                    writer.Emit(OpCodes.Ret);
                    writer.Body.OptimizeMacros();


                    {
                        method.Body.Instructions.Clear();
                        method.Body.Variables.Clear();
                        var oldWriter = method.Body.GetILProcessor();
                        oldWriter.Emit(OpCodes.Ldsfld, expressionField);
                        for (int i = 0; i < allParameters.Count; i++)
                        {
                            oldWriter.Emit(OpCodes.Ldarg, allParameters[i]);
                        }

                        var evaluate         = ExpressionExtensions.Methods.Single(a => a.Name == "Evaluate" && a.GenericParameters.Count == allParameters.Count + 1);
                        var evaluateInstance = new GenericInstanceMethod(evaluate);
                        foreach (var p in allParameters)
                        {
                            evaluateInstance.GenericArguments.Add(p.ParameterType);
                        }
                        evaluateInstance.GenericArguments.Add(method.ReturnType);

                        oldWriter.Emit(OpCodes.Call, mod.ImportReference(evaluateInstance));
                        oldWriter.Emit(OpCodes.Ret);
                        oldWriter.Body.Optimize();
                    }
                    return;
                }
                else if (reader.TryGet(OpCodes.Ldloc) is Instruction ldloc)
                {
                    writer.Emit(ldloc.OpCode, rebasedVariables[(VariableDefinition)ldloc.Operand]);
                }
                else if (reader.TryGet(OpCodes.Stloc) is Instruction stloc)
                {
                    writer.Emit(stloc.OpCode, rebasedVariables[(VariableDefinition)stloc.Operand]);
                }
                else if (reader.TryGet(OpCodes.Ldloca) is Instruction ldloca)
                {
                    writer.Emit(ldloca.OpCode, rebasedVariables[(VariableDefinition)ldloca.Operand]);
                }
                else
                {
                    var ins = reader.Get();
                    writer.Append(ins);
                }
            }

            throw new InvalidOperationException($"The method {method.FullName} should only call As.Expression with an expression tree lambda");
        }