internal Type Build(Type parent, Type target, IIntrospect introspector) { Assertion.IsNotNull("parent", parent); Assertion.IsNotNull("target", target); var ident = Ident.Create(parent, target); Type type; if (Built.TryGetValue(ident, out type)) { return(type); } lock (Built) { if (Built.TryGetValue(ident, out type)) { return(type); } return(Built[ident] = Build(ident, introspector)); } }
Type Build(Ident ident, IIntrospect introspector) { var parent = ident.Item1; var target = ident.Item2; Assertion.IsNotNull("introspector", introspector); Assertion.IsAccessible("parent", parent); Assertion.IsAccessible("target", target); Assertion.IsClass("parent", parent); Assertion.ExtendsSkeleton("parent", parent); Assertion.IsNotSealed("parent", parent); Assertion.IsInterface("target", target); var methods = Methods(target).Where(ReflectionExtensions.IsMethod); var maps = parent.GetInterfaces() .Select(parent.GetInterfaceMap) .SelectMany(ZipMap) .ToDictionary(x => x.Item1, x => x.Item2); var name = parent.Derived(target); var typB = ModB.DefineType(name: name, attr: TypeAttributes.Class, parent: parent, interfaces: new [] { target }); foreach (var method in methods) { MethodInfo over; if (maps.TryGetValue(method, out over) && !over.IsAbstract) { continue; } Assertion.IsAsync(method); var call = introspector.Call(parent, method); Assertion.HasValidDescription(method, call); call.Headers = call.Headers ?? new Headers(); call.Body = call.Body ?? new Body(); call.Static = call.Static ?? new Static(); var args = method.GetParameters(); var metB = typB.DefineMethod(name: method.DeclaringType.Name + "." + method.Name, attributes: method.Attributes & ~MethodAttributes.Abstract, callingConvention: method.CallingConvention, returnType: method.ReturnType, parameterTypes: args.Select(x => x.ParameterType).ToArray()); var ilG = metB.GetILGenerator(); typB.DefineMethodOverride(metB, method); if (null != over) { typB.DefineMethodOverride(metB, over); } // Separate parameters var bodyP = call.Body.Keys.ToArray(); var headP = call.Headers.Keys.ToArray(); var restP = args.Except(bodyP.Union(headP)).ToArray(); var caTok = args.FirstOrDefault(x => typeof(CancellationToken?).IsAssignableFrom(x.ParameterType)); // Decide immediately if we can tail call from SendAsync var thrmT = typeof(Task <HttpResponseMessage>); var tail = thrmT == method.ReturnType; var ctHM = typeof(HttpMethod).GetConstructor(new [] { typeof(string) }); ilG.Emit(OpCodes.Ldarg_0); if (!tail) { ilG.Emit(OpCodes.Dup); } ilG.Emit(OpCodes.Ldstr, call.Verb.Method); ilG.Emit(OpCodes.Newobj, ctHM); ilG.EmitString(call.Template); var dict = typeof(Params); var ctor = dict.GetConstructor(EmpT); var madd = dict.GetProperty("Item").SetMethod; // Construct parameters dictionary ilG.Emit(OpCodes.Newobj, ctor); foreach (var arg in restP) { ilG.Emit(OpCodes.Dup); ilG.Emit(OpCodes.Ldstr, arg.Name); ilG.Emit(OpCodes.Ldarg, arg.Position + 1); if (arg.ParameterType.IsValueType) { ilG.Emit(OpCodes.Box, arg.ParameterType); } ilG.Emit(OpCodes.Call, madd); } // Construct headers dictionary ilG.Emit(OpCodes.Newobj, ctor); // Pack static headers, but ignore the ones we would replace foreach (var head in call.Static) { ilG.Emit(OpCodes.Dup); ilG.Emit(OpCodes.Ldstr, head.Key); ilG.Emit(OpCodes.Ldc_I4, head.Value.Length); ilG.Emit(OpCodes.Newarr, typeof(string)); for (int i = 0, end = head.Value.Length; i < end; ++i) { ilG.Emit(OpCodes.Dup); ilG.Emit(OpCodes.Ldc_I4, i); ilG.Emit(OpCodes.Ldstr, head.Value[i]); ilG.Emit(OpCodes.Stelem, typeof(string)); } ilG.Emit(OpCodes.Call, madd); } // Pack parameter headers foreach (var head in headP) { ilG.Emit(OpCodes.Dup); ilG.Emit(OpCodes.Ldstr, call.Headers[head]); ilG.Emit(OpCodes.Ldarg, head.Position + 1); if (head.ParameterType.IsValueType) { ilG.Emit(OpCodes.Box, head.ParameterType); } ilG.Emit(OpCodes.Call, madd); } var delM = GetMethod(parent, "SendAsync", null, SendAsyncParams()); // Build content if (0 == bodyP.Length) { ilG.Emit(OpCodes.Ldnull); } else { var imp = Imp; var ctb = typeof(SkeletonClient).GetMethod("ContentBuilder", BindingFlags.NonPublic | BindingFlags.Instance); ilG.Emit(OpCodes.Ldarg_0); ilG.Emit(OpCodes.Callvirt, ctb); imp.Process(ilG, call); } // Load cancellation token if (caTok != null) { ilG.Emit(OpCodes.Ldarg, caTok.Position + 1); ilG.EmitNullConversion(caTok.ParameterType); } else { LocalBuilder caTokF = ilG.DeclareLocal(typeof(CancellationToken?)); ilG.Emit(OpCodes.Ldloca, caTokF); ilG.Emit(OpCodes.Initobj, typeof(CancellationToken?)); ilG.Emit(OpCodes.Ldloc, caTokF); } // Call SendAsync if (tail) { ilG.Emit(OpCodes.Tailcall); } ilG.EmitCall(delM); // Convert return type if necessary if (!tail) { var conM = method.ReturnType.IsGenericType ? GetMethod(parent, "Return", method.ReturnType.GetGenericArguments(), new [] { thrmT }) : GetMethod(parent, "Return", null, new [] { thrmT }); ilG.Emit(OpCodes.Tailcall); ilG.EmitCall(conM); } ilG.Emit(OpCodes.Ret); } // Generate constructors foreach (var ctor in parent.GetConstructors(BindingFlags.Public | BindingFlags.Instance)) { var ctorP = ctor.GetParameters(); var ctorB = typB.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard | CallingConventions.HasThis, ctorP.Select(x => x.ParameterType).ToArray()); var ilG = ctorB.GetILGenerator(); ilG.Emit(OpCodes.Ldarg_0); foreach (var param in ctorP) { var p = ctorB.DefineParameter(param.Position + 1, param.Attributes, param.Name); ilG.Emit(OpCodes.Ldarg, p.Position); } ilG.Emit(OpCodes.Call, ctor); ilG.Emit(OpCodes.Ret); } return(typB.CreateType()); }