/// <summary>
        /// Generates the proxy for specified service type.
        /// </summary>
        public static Tuple <Type, MethodInfo[]> Generate(Type serviceType)
        {
            Debug.Assert(serviceType != null);
            Debug.Assert(serviceType.FullName != null);

            var isClass   = serviceType.IsClass;
            var proxyType = ModuleBuilder.DefineType(serviceType.FullName,
                                                     TypeAttributes.Class, isClass ? serviceType : null);

            var buildContext = new ProxyBuildContext(proxyType, serviceType);

            if (!isClass)
            {
                proxyType.AddInterfaceImplementation(serviceType);
            }

            GenerateFields(buildContext);
            GenerateStaticConstructor(buildContext);
            GenerateConstructor(buildContext);

            Debug.Assert(Finalizer != null);

            buildContext.Methods = ReflectionUtils.GetMethods(buildContext.ServiceType)
                                   .Where(m => m.IsVirtual && m != Finalizer)
                                   .ToArray();

            for (var i = 0; i < buildContext.Methods.Length; i++)
            {
                GenerateMethod(buildContext, i);
            }

            var type = proxyType.CreateType();

            return(Tuple.Create(type, buildContext.Methods));
        }
        /// <summary>
        /// Generates the static constructor (type initializer).
        /// </summary>
        private static void GenerateStaticConstructor(ProxyBuildContext buildContext)
        {
            var cb = buildContext.ProxyType.DefineConstructor(
                MethodAttributes.Static | MethodAttributes.Private | MethodAttributes.HideBySig,
                CallingConventions.Standard, new Type[0]);
            var gen = cb.GetILGenerator();
            //fill _emptyParameters field
            gen.Emit(OpCodes.Ldc_I4_0);
            gen.Emit(OpCodes.Newarr, typeof(object));
            gen.Emit(OpCodes.Stsfld, buildContext.EmptyParametersField);

            gen.Emit(OpCodes.Ret);
        }
        /// <summary>
        /// Generates readonly fields: action and method array.
        /// </summary>
        private static void GenerateFields(ProxyBuildContext buildContext)
        {
            // Static field - empty object array to optimize calls without parameters.
            buildContext.EmptyParametersField = buildContext.ProxyType.DefineField("_emptyParameters",
                                                                                   typeof(object[]), FieldAttributes.Static | FieldAttributes.Private | FieldAttributes.InitOnly);

            // Instance field for function to invoke.
            buildContext.ActionField = buildContext.ProxyType.DefineField("_action", ActionType,
                                                                          FieldAttributes.Private | FieldAttributes.InitOnly);

            // Field - array with methods of service's type.
            buildContext.MethodsField = buildContext.ProxyType.DefineField("_methods", typeof(MethodInfo[]),
                                                                           FieldAttributes.Private | FieldAttributes.InitOnly);
        }
        /// <summary>
        /// Generates the constructor which calls base class (when necessary) and initializes fields.
        /// </summary>
        private static void GenerateConstructor(ProxyBuildContext buildContext)
        {
            var baseType = buildContext.ServiceType;
            var isClass  = baseType.IsClass;

            ConstructorInfo baseCtr = null;

            if (isClass)
            {
                baseCtr = baseType.GetConstructor(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public,
                                                  null, new Type[0], null);
                if (baseCtr == null)
                {
                    throw new NotSupportedException(
                              "Service proxy does not support base types without parameterless constructor: " +
                              baseType.FullName);
                }
            }

            var cb = buildContext.ProxyType.DefineConstructor(MethodAttributes.Public, CallingConventions.HasThis,
                                                              new[] { ActionType, typeof(MethodInfo[]) });
            var gen = cb.GetILGenerator();

            if (isClass)
            {
                // Load "this".
                gen.Emit(OpCodes.Ldarg_0);
                // Call base constructor.
                gen.Emit(OpCodes.Call, baseCtr);
            }

            // Assign parameters to fields.
            gen.Emit(OpCodes.Ldarg_0);
            gen.Emit(OpCodes.Ldarg_1);
            gen.Emit(OpCodes.Stfld, buildContext.ActionField);

            gen.Emit(OpCodes.Ldarg_0);
            gen.Emit(OpCodes.Ldarg_2);
            gen.Emit(OpCodes.Stfld, buildContext.MethodsField);

            gen.Emit(OpCodes.Ret);
        }
        /// <summary>
        /// Generates the overriding method which delegates to ProxyAction.
        /// </summary>
        private static void GenerateMethod(ProxyBuildContext buildContext, int methodIndex)
        {
            var method = buildContext.Methods[methodIndex];

            Debug.Assert(method.DeclaringType != null);
            var parameters     = method.GetParameters();
            var parameterTypes = new Type[parameters.Length];

            for (var i = 0; i < parameters.Length; i++)
            {
                parameterTypes[i] = parameters[i].ParameterType;
            }

            var attributes = MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.HideBySig;

            if (method.DeclaringType.IsInterface)
            {
                attributes |= MethodAttributes.Final | MethodAttributes.NewSlot;
            }
            if ((method.Attributes & MethodAttributes.SpecialName) == MethodAttributes.SpecialName)
            {
                attributes |= MethodAttributes.SpecialName;
            }
            var methodBuilder =
                buildContext.ProxyType.DefineMethod(method.Name, attributes, method.ReturnType, parameterTypes);
            var gen = methodBuilder.GetILGenerator();

            // Prepare arguments for action invocation.

            // Load action field.
            gen.Emit(OpCodes.Ldarg_0);
            gen.Emit(OpCodes.Ldfld, buildContext.ActionField);

            // Load methods array field.
            gen.Emit(OpCodes.Ldarg_0);
            gen.Emit(OpCodes.Ldfld, buildContext.MethodsField);

            // Load index of method.
            gen.Emit(OpCodes.Ldc_I4, methodIndex);

            // Load array element.
            gen.Emit(OpCodes.Ldelem_Ref);

            if (parameters.Length > 0)
            {
                // Create array for action's parameters.
                gen.Emit(OpCodes.Ldc_I4, parameters.Length);
                gen.Emit(OpCodes.Newarr, typeof(object));

                // Fill array.
                // Load call arguments.
                for (var i = 0; i < parameters.Length; i++)
                {
                    gen.Emit(OpCodes.Dup);

                    // Parameter's index in array.
                    gen.Emit(OpCodes.Ldc_I4, i);

                    // Parameter's value.
                    gen.Emit(OpCodes.Ldarg, i + 1);
                    if (parameterTypes[i].IsValueType)
                    {
                        gen.Emit(OpCodes.Box, parameterTypes[i]);
                    }

                    // Set array's element
                    gen.Emit(OpCodes.Stelem_Ref);
                }
            }
            else
            {
                // Load static empty parameters field.
                gen.Emit(OpCodes.Ldsfld, buildContext.EmptyParametersField);
            }

            // Call action method.
            gen.Emit(OpCodes.Callvirt, InvokeMethod);

            // Load result.
            if (method.ReturnType != typeof(void))
            {
                if (method.ReturnType.IsValueType)
                {
                    gen.Emit(OpCodes.Unbox_Any, method.ReturnType);
                }
            }
            else
            {
                // Method should not return result, so remove result from stack.
                gen.Emit(OpCodes.Pop);
            }
            //exit
            gen.Emit(OpCodes.Ret);
        }