static Func <TContext, ValueBindingContext, IValueProvider> BuildICollectorArgument <TContext, TMessageSrc, TMessage>(
            IConverterManager cm,
            Func <TContext, ValueBindingContext, IFlushCollector <TMessage> > builder,
            string invokeString
            )
        {
            // Other
            Func <TMessageSrc, TMessage> convert = cm.GetConverter <TMessageSrc, TMessage>();
            Func <TContext, ValueBindingContext, IValueProvider> argumentBuilder = (context, valueBindingContext) =>
            {
                IFlushCollector <TMessage>    raw  = builder(context, valueBindingContext);
                IAsyncCollector <TMessageSrc> obj  = new TypedAsyncCollectorAdapter <TMessageSrc, TMessage>(raw, convert);
                ICollector <TMessageSrc>      obj2 = new SyncAsyncCollectorAdapter <TMessageSrc>(obj);
                return(new CommonAsyncCollectorValueProvider <ICollector <TMessageSrc>, TMessage>(obj2, raw, invokeString));
            };

            return(argumentBuilder);
        }
        // Bind an IAsyncCollector<TMessage> to a user parameter.
        // This handles the various flavors of parameter types and will morph them to the connector.
        // parameter  - parameter being bound.
        // TContext - helper object to pass to the binding the configuration state. This can point back to context like secrets, configuration, etc.
        // builder - function to create a new instance of the underlying Collector object to pass to the parameter.
        //          This binder will wrap that in any adpaters to make it match the requested parameter type.
        public static IBinding BindCollector <TMessage, TContext>(
            ParameterInfo parameter,
            IConverterManager converterManager,
            TContext client,
            Func <TContext, ValueBindingContext, IFlushCollector <TMessage> > builder,
            string invokeString,
            Func <string, TContext> invokeStringBinder)
        {
            Type parameterType = parameter.ParameterType;

            Func <TContext, ValueBindingContext, IValueProvider> argumentBuilder = null;

            if (parameterType.IsGenericType)
            {
                var genericType = parameterType.GetGenericTypeDefinition();
                var elementType = parameterType.GetGenericArguments()[0];

                if (genericType == typeof(IAsyncCollector <>))
                {
                    if (elementType == typeof(TMessage))
                    {
                        // Bind to IAsyncCollector<TMessage>. This is the "purest" binding, no adaption needed.
                        argumentBuilder = (context, valueBindingContext) =>
                        {
                            IFlushCollector <TMessage> raw = builder(context, valueBindingContext);
                            return(new CommonAsyncCollectorValueProvider <IAsyncCollector <TMessage>, TMessage>(raw, raw, invokeString));
                        };
                    }
                    else
                    {
                        // Bind to IAsyncCollector<T>
                        // Get a converter from T to TMessage
                        argumentBuilder = DynamicInvokeBuildIAsyncCollectorArgument(elementType, converterManager, builder, invokeString);
                    }
                }
                else if (genericType == typeof(ICollector <>))
                {
                    if (elementType == typeof(TMessage))
                    {
                        // Bind to ICollector<TMessage> This just needs an Sync/Async wrapper
                        argumentBuilder = (context, valueBindingContext) =>
                        {
                            IFlushCollector <TMessage> raw = builder(context, valueBindingContext);
                            ICollector <TMessage>      obj = new SyncAsyncCollectorAdapter <TMessage>(raw);
                            return(new CommonAsyncCollectorValueProvider <ICollector <TMessage>, TMessage>(obj, raw, invokeString));
                        };
                    }
                    else
                    {
                        // Bind to ICollector<T>.
                        // This needs both a conversion from T to TMessage and an Sync/Async wrapper
                        argumentBuilder = DynamicInvokeBuildICollectorArgument(elementType, converterManager, builder, invokeString);
                    }
                }
            }

            if (parameter.IsOut)
            {
                Type elementType = parameter.ParameterType.GetElementType();

                if (elementType.IsArray)
                {
                    if (elementType == typeof(TMessage[]))
                    {
                        argumentBuilder = (context, valueBindingContext) =>
                        {
                            IFlushCollector <TMessage> raw = builder(context, valueBindingContext);
                            return(new OutArrayValueProvider <TMessage>(raw, invokeString));
                        };
                    }
                    else
                    {
                        // out TMessage[]
                        var e2 = elementType.GetElementType();
                        argumentBuilder = DynamicBuildOutArrayArgument(e2, converterManager, builder, invokeString);
                    }
                }
                else
                {
                    // Single enqueue
                    //    out TMessage
                    if (elementType == typeof(TMessage))
                    {
                        argumentBuilder = (context, valueBindingContext) =>
                        {
                            IFlushCollector <TMessage> raw = builder(context, valueBindingContext);
                            return(new OutValueProvider <TMessage>(raw, invokeString));
                        };
                    }
                    else
                    {
                        // use JSon converter
                        // out T
                        argumentBuilder = DynamicInvokeBuildOutArgument(elementType, converterManager, builder, invokeString);
                    }
                }
            }

            if (argumentBuilder != null)
            {
                ParameterDescriptor param = new ParameterDescriptor {
                    Name         = parameter.Name,
                    DisplayHints = new ParameterDisplayHints
                    {
                        Description = "output"
                    }
                };
                return(new CommonCollectorBinding <TMessage, TContext>(client, argumentBuilder, param, invokeStringBinder));
            }

            string msg = string.Format(CultureInfo.CurrentCulture, "Can't bind to {0}.", parameter);

            throw new InvalidOperationException(msg);
        }