private static object BindInstance(Type type, object instance, IConfiguration config, BinderOptions options,
                                           IEnumerable <ICustomConfigurationBinder> customBinders = null)
        {
            // if binding IConfigurationSection, break early
            if (type == typeof(IConfigurationSection))
            {
                return(config);
            }

            var section     = config as IConfigurationSection;
            var configValue = section?.Value;

            if (configValue != null && TryConvertValue(type, configValue, out var convertedValue, out var error))
            {
                if (error != null)
                {
                    throw error;
                }

                // Leaf nodes are always reinitialized
                return(convertedValue);
            }

            if (config == null || !config.GetChildren().Any())
            {
                return(instance);
            }

            // If we don't have an instance, try to create one
            if (instance == null)
            {
                // We are already done if binding to a new collection instance worked
                instance = AttemptBindToCollectionInterfaces(type, config, options, customBinders);
                if (instance != null)
                {
                    return(instance);
                }
                instance = CreateInstance(ref type);
            }

            // See if its a Dictionary
            var collectionInterface = FindOpenGenericInterface(typeof(IDictionary <,>), type);

            if (collectionInterface != null)
            {
                BindDictionary(instance, collectionInterface, config, options, customBinders);
            }
            else if (type.IsArray)
            {
                instance = BindArray((Array)instance, config, options, customBinders);
            }
            else
            {
                // See if its an ICollection
                collectionInterface = FindOpenGenericInterface(typeof(ICollection <>), type);
                if (collectionInterface != null)
                {
                    BindCollection(instance, collectionInterface, config, options, customBinders);
                }
                // Something else
                else
                {
                    BindNonScalar(config, ref instance, options, customBinders);
                }
            }

            return(instance);
        }
        private static void SetMembers(IConfiguration configuration, object instance, BinderOptions options,
                                       AccessorMembers members, ITypeReadAccessor read, ITypeWriteAccessor write,
                                       IEnumerable <ICustomConfigurationBinder> customBinders)
        {
            foreach (var member in members)
            {
                // We don't support set only, non-public, or indexer properties
                if (!member.CanRead || member.MemberInfo is MethodInfo method && method.GetParameters().Length > 0)
                {
                    continue;
                }

                var value = read[instance, member.Name];
                if (value == null && !member.CanWrite)
                {
                    // Property doesn't have a value and we cannot set it so there is no
                    // point in going further down the graph
                    continue;
                }

                var config = configuration.GetSection(member.Name);
                value = BindInstance(member.Type, value, config, options, customBinders);
                if (value == default || !member.CanWrite)
                {
                    continue;
                }

                write.TrySetValue(instance, member.Name, value);
            }
        }
        // Try to create an array/dictionary instance to back various collection interfaces
        private static object AttemptBindToCollectionInterfaces(Type type, IConfiguration config, BinderOptions options,
                                                                IEnumerable <ICustomConfigurationBinder> customBinders)
        {
            var typeInfo = type.GetTypeInfo();

            if (!typeInfo.IsInterface)
            {
                return(null);
            }

            var collectionInterface = FindOpenGenericInterface(typeof(IReadOnlyList <>), type);

            if (collectionInterface != null)
            {
                return(BindToCollection(typeInfo, config, options, customBinders));
            }

            collectionInterface = FindOpenGenericInterface(typeof(IReadOnlyDictionary <,>), type);
            if (collectionInterface != null)
            {
                var dictionaryType = typeof(Dictionary <,>).MakeGenericType(typeInfo.GenericTypeArguments[0],
                                                                            typeInfo.GenericTypeArguments[1]);
                var instance = CreateInstance(ref dictionaryType);
                BindDictionary(instance, dictionaryType, config, options, customBinders);
                return(instance);
            }

            collectionInterface = FindOpenGenericInterface(typeof(IDictionary <,>), type);
            if (collectionInterface != null)
            {
                var dictionaryType = typeof(Dictionary <,>).MakeGenericType(typeInfo.GenericTypeArguments[0],
                                                                            typeInfo.GenericTypeArguments[1]);
                var instance = CreateInstance(ref dictionaryType);
                BindDictionary(instance, collectionInterface, config, options, customBinders);
                return(instance);
            }

            collectionInterface = FindOpenGenericInterface(typeof(IReadOnlyCollection <>), type);
            if (collectionInterface != null)
            {
                return(BindToCollection(typeInfo, config, options, customBinders));
            }

            collectionInterface = FindOpenGenericInterface(typeof(ICollection <>), type);
            if (collectionInterface != null)
            {
                return(BindToCollection(typeInfo, config, options, customBinders));
            }

            collectionInterface = FindOpenGenericInterface(typeof(IEnumerable <>), type);
            return(collectionInterface != null?BindToCollection(typeInfo, config, options, customBinders) : null);
        }
        private static void BindNonScalar(this IConfiguration configuration, ref object instance, BinderOptions options,
                                          IEnumerable <ICustomConfigurationBinder> customBinders)
        {
            if (instance == null)
            {
                return;
            }

            var scope = AccessorMemberScope.Public;

            if (options.BindNonPublicProperties)
            {
                scope |= AccessorMemberScope.Private;
            }

            var type  = instance.GetType();
            var read  = ReadAccessor.Create(type, AccessorMemberTypes.Properties, scope, out var members);
            var write = WriteAccessor.Create(type, AccessorMemberTypes.Properties, scope);

            if (IsTypeDiscriminated(type, out _))
            {
                // Set base properties so the converter has the right values to work with
                SetMembers(configuration, instance, options, members, read, write, customBinders);

                // Give a custom converter a chance to change what is bound
                var converter = TypeDescriptor.GetConverter(type);
                if (converter.CanConvertFrom(type))
                {
                    instance = converter.ConvertFrom(instance);
                    if (instance != null)
                    {
                        type  = instance.GetType();
                        read  = ReadAccessor.Create(type, AccessorMemberTypes.Properties, scope, out members);
                        write = WriteAccessor.Create(type, AccessorMemberTypes.Properties, scope);
                    }
                }
                else
                {
                    foreach (var binder in customBinders ?? Enumerable.Empty <ICustomConfigurationBinder>())
                    {
                        if (!binder.CanConvertFrom(type))
                        {
                            continue;
                        }

                        var subType = binder.GetTypeFor(instance);
                        if (subType == null)
                        {
                            continue;
                        }

                        type     = subType;
                        instance = Instancing.CreateInstance(type);
                        read     = ReadAccessor.Create(type, AccessorMemberTypes.Properties, scope, out members);
                        write    = WriteAccessor.Create(type, AccessorMemberTypes.Properties, scope);
                        goto setMembers;
                    }
                }
            }

setMembers:
            SetMembers(configuration, instance, options, members, read, write, customBinders);
        }