private ContinuationClassInfo GetContinuationClass(ModuleDefinition module, TypeReference taskReturnType)
    {
        if (taskReturnType.MetadataType == MetadataType.Void)
        {
            return(GetContinuationClassCore(module, true));
        }

        ContinuationClassInfo genClassInfo = GetContinuationClassCore(module, false);
        GenericInstanceType   instType     = new GenericInstanceType(genClassInfo.ContinuationClass);

        instType.GenericArguments.Add(taskReturnType);
        return(new ContinuationClassInfo()
        {
            ContinuationClass = instType,
            CallbackField = GetReferenceForInstantiatedType(genClassInfo.CallbackField, instType),
            TcsField = GetReferenceForInstantiatedType(genClassInfo.TcsField, instType),
            Constructor = GetReferenceForInstantiatedType(genClassInfo.Constructor, instType),
            InvokeMethod = GetReferenceForInstantiatedType(genClassInfo.InvokeMethod, instType)
        });
    }
    private ContinuationClassInfo GetContinuationClassCore(ModuleDefinition module, bool isVoid)
    {
        if (isVoid && contClassForVoid != null)
        {
            return(contClassForVoid);
        }
        if (!isVoid && contClassForNonVoid != null)
        {
            return(contClassForNonVoid);
        }

        ContinuationClassInfo ci       = new ContinuationClassInfo();
        TypeDefinition        classDef = new TypeDefinition("", "<asyncContinuationClass>" + (isVoid ? "" : "`1"),
                                                            TypeAttributes.Sealed, module.TypeSystem.Object);

        classDef.CustomAttributes.Add(generatedCodeAttr);
        TypeReference taskReturnType, selfInst;

        if (!isVoid)
        {
            classDef.GenericParameters.Add(new GenericParameter("T", classDef));
            taskReturnType = classDef.GenericParameters[0];
            selfInst       = new GenericInstanceType(classDef);
            ((GenericInstanceType)selfInst).GenericArguments.Add(classDef.GenericParameters[0]);
        }
        else
        {
            taskReturnType = module.TypeSystem.Object;
            selfInst       = classDef;
        }

        ci.ContinuationClass = classDef;

        GenericInstanceType genTaskType = new GenericInstanceType(taskOpenType);

        genTaskType.GenericArguments.Add(taskReturnType);
        TypeReference taskType = isVoid ? taskBaseType : genTaskType;

        FieldDefinition callbackGen = new FieldDefinition("Callback", FieldAttributes.Public, asyncCallbackType);

        classDef.Fields.Add(callbackGen);
        ci.CallbackField = callbackGen;
        var callback = new FieldReference(callbackGen.Name, callbackGen.FieldType, selfInst);

        var tcsType = new GenericInstanceType(taskCompletionSourceType);

        tcsType.GenericArguments.Add(isVoid ? module.TypeSystem.Object : taskReturnType);
        FieldDefinition tcsGen = new FieldDefinition("Tcs", FieldAttributes.Public, tcsType);

        classDef.Fields.Add(tcsGen);
        ci.TcsField = tcsGen;
        var tcs = new FieldReference(tcsGen.Name, tcsGen.FieldType, selfInst);

        var trySetExceptionRef = new MethodReference("TrySetException", module.TypeSystem.Boolean, tcsType)
        {
            HasThis = true
        };

        trySetExceptionRef.Parameters.Add(new ParameterDefinition(iEnumerableExceptionType));
        var trySetCanceledRef = new MethodReference("TrySetCanceled", module.TypeSystem.Boolean, tcsType)
        {
            HasThis = true
        };
        var trySetResultRef = new MethodReference("TrySetResult", module.TypeSystem.Boolean, tcsType)
        {
            HasThis = true
        };

        trySetResultRef.Parameters.Add(new ParameterDefinition(taskCompletionSourceType.GenericParameters[0]));
        var taskOfTcsTP0 = new GenericInstanceType(taskOpenType);

        taskOfTcsTP0.GenericArguments.Add(taskCompletionSourceType.GenericParameters[0]);
        var getTaskRef = new MethodReference("get_Task", taskOfTcsTP0, tcsType)
        {
            HasThis = true
        };

        MethodReference get_Result = null;

        if (!isVoid)
        {
            get_Result = new MethodReference("get_Result", taskOpenType.GenericParameters[0], taskType)
            {
                HasThis = true
            }
        }
        ;

        MethodDefinition ctor = new MethodDefinition(".ctor",
                                                     MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName,
                                                     module.TypeSystem.Void);

        var il = ctor.Body.GetILProcessor();

        il.Emit(OpCodes.Ldarg_0);          // this
        il.Emit(OpCodes.Call, objectCtor); // base()
        il.Emit(OpCodes.Ret);

        classDef.Methods.Add(ctor);
        ci.Constructor = ctor;

        MethodDefinition invoke = new MethodDefinition("Invoke", MethodAttributes.Public, module.TypeSystem.Void);
        var taskArg             = new ParameterDefinition(taskType);

        invoke.Parameters.Add(taskArg);

        il = invoke.Body.GetILProcessor();
        il.Emit(OpCodes.Ldarg_1); // task
        il.Emit(OpCodes.Callvirt, task_get_IsFaultedMethod);
        var notFaultedCont = Instruction.Create(OpCodes.Ldarg_1);

        il.Emit(OpCodes.Brfalse_S, notFaultedCont);

        il.Emit(OpCodes.Ldarg_0);
        il.Emit(OpCodes.Ldfld, tcs);
        il.Emit(OpCodes.Ldarg_1);
        il.Emit(OpCodes.Callvirt, task_get_ExceptionMethod);
        il.Emit(OpCodes.Callvirt, get_InnerExceptionsMethod);
        il.Emit(OpCodes.Callvirt, trySetExceptionRef);
        il.Emit(OpCodes.Pop);
        var callbackCont = Instruction.Create(OpCodes.Ldarg_0);

        il.Emit(OpCodes.Br_S, callbackCont);

        il.Append(notFaultedCont); // ldarg.1
        il.Emit(OpCodes.Callvirt, task_get_IsCanceledMethod);
        var notCanceledCont = Instruction.Create(OpCodes.Ldarg_0);

        il.Emit(OpCodes.Brfalse_S, notCanceledCont);

        il.Emit(OpCodes.Ldarg_0);
        il.Emit(OpCodes.Ldfld, tcs);
        il.Emit(OpCodes.Callvirt, trySetCanceledRef);
        il.Emit(OpCodes.Pop);
        il.Emit(OpCodes.Br_S, callbackCont);

        il.Append(notCanceledCont); // ldarg.0
        il.Emit(OpCodes.Ldfld, tcs);
        il.Emit(OpCodes.Ldarg_1);
        if (isVoid)
        {
            il.Emit(OpCodes.Callvirt, task_WaitMethod);
            il.Emit(OpCodes.Ldnull);
        }
        else
        {
            il.Emit(OpCodes.Callvirt, get_Result);
        }
        il.Emit(OpCodes.Callvirt, trySetResultRef);
        il.Emit(OpCodes.Pop);

        il.Append(callbackCont); // ldarg.0
        il.Emit(OpCodes.Ldfld, callback);
        il.Emit(OpCodes.Dup);
        var endPopRet = Instruction.Create(OpCodes.Pop);

        il.Emit(OpCodes.Brfalse_S, endPopRet);

        il.Emit(OpCodes.Ldarg_0);
        il.Emit(OpCodes.Ldfld, tcs);
        il.Emit(OpCodes.Callvirt, getTaskRef);
        il.Emit(OpCodes.Callvirt, asyncCallbackInvokeMethod);

        il.Emit(OpCodes.Ret);

        il.Append(endPopRet); // pop
        il.Emit(OpCodes.Ret);

        classDef.Methods.Add(invoke);
        ci.InvokeMethod = invoke;

        module.Types.Add(classDef);

        if (isVoid)
        {
            contClassForVoid = ci;
        }
        else
        {
            contClassForNonVoid = ci;
        }
        return(ci);
    }