Update binding generator to do case-insensitive config-key/property name matching...
authorLayomi Akinrinade <laakinri@microsoft.com>
Wed, 12 Jul 2023 15:57:08 +0000 (08:57 -0700)
committerGitHub <noreply@github.com>
Wed, 12 Jul 2023 15:57:08 +0000 (08:57 -0700)
* Update binding generator to do case-insensitive config-key/property name matching, & make some formatting improvements

* Address feedback

48 files changed:
src/libraries/Microsoft.Extensions.Configuration.Binder/gen/ConfigurationBindingGenerator.Emitter.cs
src/libraries/Microsoft.Extensions.Configuration.Binder/gen/ConfigurationBindingGenerator.Parser.cs
src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Helpers/Emitter/ConfigurationBinder.cs
src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Helpers/Emitter/CoreBindingHelper.cs
src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Helpers/Emitter/Helpers.cs
src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Helpers/Emitter/OptionsBuilderConfigurationExtensions.cs
src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Helpers/Emitter/OptionsConfigurationServiceCollectionExtensions.cs
src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Helpers/MethodsToGen.cs
src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Helpers/Parser/ConfigurationBinder.cs
src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Helpers/SourceWriter.cs
src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Microsoft.Extensions.Configuration.Binder.SourceGeneration.csproj
src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Model/CollectionSpec.cs
src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Model/MemberSpec.cs [new file with mode: 0644]
src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Model/NullableSpec.cs
src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Model/ObjectSpec.cs
src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Model/ParameterSpec.cs
src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Model/ParsableFromStringSpec.cs
src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Model/PropertySpec.cs
src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Model/SourceGenerationSpec.cs
src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Model/TypeSpec.cs
src/libraries/Microsoft.Extensions.Configuration.Binder/tests/Common/ConfigurationBinderTests.Collections.cs
src/libraries/Microsoft.Extensions.Configuration.Binder/tests/Common/ConfigurationBinderTests.TestClasses.cs
src/libraries/Microsoft.Extensions.Configuration.Binder/tests/Common/ConfigurationBinderTests.cs
src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/Collections.generated.txt
src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ConfigurationBinder/Bind.generated.txt
src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ConfigurationBinder/Bind_Instance.generated.txt
src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ConfigurationBinder/Bind_Instance_BinderOptions.generated.txt
src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ConfigurationBinder/Bind_Key_Instance.generated.txt
src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ConfigurationBinder/Get.generated.txt
src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ConfigurationBinder/GetValue.generated.txt
src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ConfigurationBinder/GetValue_T_Key.generated.txt
src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ConfigurationBinder/GetValue_T_Key_DefaultValue.generated.txt
src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ConfigurationBinder/GetValue_TypeOf_Key.generated.txt
src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ConfigurationBinder/GetValue_TypeOf_Key_DefaultValue.generated.txt
src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ConfigurationBinder/Get_T.generated.txt
src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ConfigurationBinder/Get_T_BinderOptions.generated.txt
src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ConfigurationBinder/Get_TypeOf.generated.txt
src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ConfigurationBinder/Get_TypeOf_BinderOptions.generated.txt
src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/OptionsBuilder/BindConfiguration.generated.txt
src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/OptionsBuilder/Bind_T.generated.txt
src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/OptionsBuilder/Bind_T_BinderOptions.generated.txt
src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/Primitives.generated.txt
src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ServiceCollection/Configure_T.generated.txt
src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ServiceCollection/Configure_T_BinderOptions.generated.txt
src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ServiceCollection/Configure_T_name.generated.txt
src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Baselines/ServiceCollection/Configure_T_name_BinderOptions.generated.txt
src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/ConfigurationBindingGeneratorTests.cs
src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Microsoft.Extensions.Configuration.Binder.SourceGeneration.Tests.csproj

index f242ca3..f24a43c 100644 (file)
@@ -15,17 +15,13 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
             private readonly SourceProductionContext _context;
             private readonly SourceGenerationSpec _sourceGenSpec;
 
-            // Postfix for stringValueX variables used to save config value indexer
-            // results e.g. if (configuration["Key"] is string stringValue0) { ... }
-            private int _parseValueCount;
-
-            private bool _precedingBlockExists;
-
-            private readonly SourceWriter _writer = new();
+            private bool _emitBlankLineBeforeNextStatement;
+            private bool _useFullyQualifiedNames;
+            private int _valueSuffixIndex;
 
             private static readonly Regex s_arrayBracketsRegex = new(Regex.Escape("[]"));
 
-            public bool _useFullyQualifiedNames { get; private set; }
+            private readonly SourceWriter _writer = new();
 
             public Emitter(SourceProductionContext context, SourceGenerationSpec sourceGenSpec)
             {
@@ -40,9 +36,13 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
                     return;
                 }
 
-                _writer.WriteLine(@"// <auto-generated/>
-#nullable enable
-");
+                _writer.WriteBlock("""
+                    // <auto-generated/>
+                    #nullable enable
+                    #pragma warning disable CS0612, CS0618 // Suppress warnings about [Obsolete] member usage in generated code.
+                    """);
+                _writer.WriteBlankLine();
+
                 _useFullyQualifiedNames = true;
                 EmitBinder_ConfigurationBinder();
                 EmitBinder_Extensions_OptionsBuilder();
@@ -54,119 +54,98 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
                 _context.AddSource($"{Identifier.GeneratedConfigurationBinder}.g.cs", _writer.ToSourceText());
             }
 
-            private void EmitBindLogicFromRootMethod(TypeSpec type, string expressionForMemberAccess, InitializationKind initKind)
-            {
-                TypeSpecKind kind = type.SpecKind;
-
-                if (kind is TypeSpecKind.Nullable)
-                {
-                    EmitBindLogicFromRootMethod(((NullableSpec)type).UnderlyingType, expressionForMemberAccess, initKind);
-                }
-                else
-                {
-                    if (type is ParsableFromStringSpec stringParsableType)
-                    {
-                        if (initKind is InitializationKind.Declaration)
-                        {
-                            EmitCastToIConfigurationSection();
-                            _writer.WriteLine($"{GetTypeDisplayString(type)} {expressionForMemberAccess} = default!;");
-                        }
-                        else
-                        {
-                            EmitCastToIConfigurationSection();
-                        }
-
-                        EmitBindLogicFromString(stringParsableType, Expression.sectionValue, Expression.sectionPath);
-                    }
-                    else
-                    {
-                        EmitBindCoreCall(type, expressionForMemberAccess, Identifier.configuration, initKind);
-                    }
-                }
-            }
-
             private void EmitBindCoreCall(
                 TypeSpec type,
-                string expressionForMemberAccess,
-                string expressionForConfigArg,
-                InitializationKind initKind)
+                string memberAccessExpr,
+                string configArgExpr,
+                InitializationKind initKind,
+                Action<string>? writeOnSuccess = null)
             {
                 Debug.Assert(type.CanInitialize);
 
-                string tempVarName = GetIncrementalVarName(Identifier.temp);
+                if (!type.NeedsMemberBinding)
+                {
+                    EmitObjectInit(memberAccessExpr, initKind);
+                    return;
+                }
+
+                string tempIdentifier = GetIncrementalIdentifier(Identifier.temp);
                 if (initKind is InitializationKind.AssignmentWithNullCheck)
                 {
-                    _writer.WriteLine($"{type.MinimalDisplayString} {tempVarName} = {expressionForMemberAccess};");
-                    EmitObjectInit(type, tempVarName, InitializationKind.AssignmentWithNullCheck);
-                    EmitBindCoreCall(tempVarName);
+                    _writer.WriteLine($"{type.MinimalDisplayString} {tempIdentifier} = {memberAccessExpr};");
+                    EmitBindCoreCall(tempIdentifier, InitializationKind.AssignmentWithNullCheck);
                 }
                 else if (initKind is InitializationKind.None && type.IsValueType)
                 {
-                    EmitObjectInit(type, tempVarName, InitializationKind.Declaration);
-                    _writer.WriteLine($@"{Identifier.BindCore}({expressionForConfigArg}, ref {tempVarName}, {Identifier.binderOptions});");
-                    _writer.WriteLine($"{expressionForMemberAccess} = {tempVarName};");
+                    EmitBindCoreCall(tempIdentifier, InitializationKind.Declaration);
+                    _writer.WriteLine($"{memberAccessExpr} = {tempIdentifier};");
                 }
                 else
                 {
-                    EmitObjectInit(type, expressionForMemberAccess, initKind);
-                    EmitBindCoreCall(expressionForMemberAccess);
+                    EmitBindCoreCall(memberAccessExpr, initKind);
                 }
 
-                void EmitBindCoreCall(string varName)
+                void EmitBindCoreCall(string objExpression, InitializationKind initKind)
                 {
-                    string bindCoreCall = $@"{GetHelperMethodDisplayString(Identifier.BindCore)}({expressionForConfigArg}, ref {varName}, {Identifier.binderOptions});";
+                    string methodDisplayString = GetHelperMethodDisplayString(nameof(MethodsToGen_CoreBindingHelper.BindCore));
+                    string bindCoreCall = $@"{methodDisplayString}({configArgExpr}, ref {objExpression}, {Identifier.binderOptions});";
+
+                    EmitObjectInit(objExpression, initKind);
                     _writer.WriteLine(bindCoreCall);
+                    writeOnSuccess?.Invoke(objExpression);
+                }
+
+                void EmitObjectInit(string objExpression, InitializationKind initKind)
+                {
+                    if (initKind is not InitializationKind.None)
+                    {
+                        this.EmitObjectInit(type, objExpression, initKind, configArgExpr);
+                    }
                 }
             }
 
-            public void EmitBindLogicFromString(
+            private void EmitBindLogicFromString(
                 ParsableFromStringSpec type,
-                string configStringValueExpr,
-                string configValuePathExpr,
-                Action<string>? writeOnSuccess = null,
-                bool isCollectionElement = false)
+                string sectionValueExpr,
+                string sectionPathExpr,
+                Action<string>? writeOnSuccess,
+                bool checkForNullSectionValue,
+                bool useIncrementalStringValueIdentifier)
             {
                 StringParsableTypeKind typeKind = type.StringParsableTypeKind;
                 Debug.Assert(typeKind is not StringParsableTypeKind.None);
 
-                string stringValueVarName = GetIncrementalVarName(Identifier.stringValue);
-                string parsedValueExpr;
+                string nonNull_StringValue_Identifier = useIncrementalStringValueIdentifier ? GetIncrementalIdentifier(Identifier.value) : Identifier.value;
+                string stringValueToParse_Expr = checkForNullSectionValue ? nonNull_StringValue_Identifier : sectionValueExpr;
 
-                if (typeKind is StringParsableTypeKind.ConfigValue)
+                string parsedValueExpr;
+                if (typeKind is StringParsableTypeKind.AssignFromSectionValue)
                 {
-                    if (isCollectionElement)
-                    {
-                        parsedValueExpr = stringValueVarName;
-                    }
-                    else
-                    {
-                        writeOnSuccess?.Invoke(configStringValueExpr);
-                        return;
-                    }
+                    parsedValueExpr = stringValueToParse_Expr;
                 }
                 else
                 {
                     string helperMethodDisplayString = GetHelperMethodDisplayString(type.ParseMethodName);
-                    parsedValueExpr = $"{helperMethodDisplayString}({stringValueVarName}, () => {configValuePathExpr})";
+                    parsedValueExpr = $"{helperMethodDisplayString}({stringValueToParse_Expr}, () => {sectionPathExpr})";
                 }
 
-                _writer.WriteBlockStart($"if ({configStringValueExpr} is string {stringValueVarName})");
-                writeOnSuccess?.Invoke(parsedValueExpr);
-                _writer.WriteBlockEnd();
-
-                return;
+                if (!checkForNullSectionValue)
+                {
+                    writeOnSuccess?.Invoke(parsedValueExpr);
+                }
+                else
+                {
+                    _writer.WriteBlockStart($"if ({sectionValueExpr} is string {nonNull_StringValue_Identifier})");
+                    writeOnSuccess?.Invoke(parsedValueExpr);
+                    _writer.WriteBlockEnd();
+                }
             }
 
-            private bool EmitObjectInit(TypeSpec type, string expressionForMemberAccess, InitializationKind initKind)
+            private bool EmitObjectInit(TypeSpec type, string memberAccessExpr, InitializationKind initKind, string configArgExpr)
             {
-                Debug.Assert(type.CanInitialize);
-
-                if (initKind is InitializationKind.None)
-                {
-                    return true;
-                }
+                Debug.Assert(type.CanInitialize && initKind is not InitializationKind.None);
 
-                string expressionForInit;
+                string initExpr;
                 CollectionSpec? collectionType = type as CollectionSpec;
 
                 string effectiveDisplayString = GetTypeDisplayString(type);
@@ -174,30 +153,29 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
                 {
                     if (collectionType is EnumerableSpec { InitializationStrategy: InitializationStrategy.Array })
                     {
-                        expressionForInit = $"new {s_arrayBracketsRegex.Replace(effectiveDisplayString, "[0]", 1)}";
+                        initExpr = $"new {s_arrayBracketsRegex.Replace(effectiveDisplayString, "[0]", 1)}";
                     }
                     else
                     {
                         effectiveDisplayString = GetTypeDisplayString(collectionType.ConcreteType ?? collectionType);
-                        expressionForInit = $"new {effectiveDisplayString}()";
+                        initExpr = $"new {effectiveDisplayString}()";
                     }
                 }
                 else if (type.InitializationStrategy is InitializationStrategy.ParameterlessConstructor)
                 {
-                    expressionForInit = $"new {effectiveDisplayString}()";
+                    initExpr = $"new {effectiveDisplayString}()";
                 }
                 else
                 {
                     Debug.Assert(type.InitializationStrategy is InitializationStrategy.ParameterizedConstructor);
-                    string expressionForConfigSection = initKind is InitializationKind.Declaration ? Identifier.configuration : Identifier.section;
-                    string initMethodIdentifier = GetHelperMethodDisplayString(((ObjectSpec)type).InitializeMethodDisplayString);
-                    expressionForInit = $"{initMethodIdentifier}({expressionForConfigSection}, {Identifier.binderOptions});";
+                    string initMethodIdentifier = GetInitalizeMethodDisplayString(((ObjectSpec)type));
+                    initExpr = $"{initMethodIdentifier}({configArgExpr}, {Identifier.binderOptions})";
                 }
 
                 if (initKind == InitializationKind.Declaration)
                 {
-                    Debug.Assert(!expressionForMemberAccess.Contains("."));
-                    _writer.WriteLine($"var {expressionForMemberAccess} = {expressionForInit};");
+                    Debug.Assert(!memberAccessExpr.Contains("."));
+                    _writer.WriteLine($"var {memberAccessExpr} = {initExpr};");
                 }
                 else if (initKind == InitializationKind.AssignmentWithNullCheck)
                 {
@@ -208,28 +186,28 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
                     {
                         if (collectionType.InitializationStrategy is InitializationStrategy.ParameterizedConstructor)
                         {
-                            _writer.WriteLine($"{expressionForMemberAccess} = {expressionForMemberAccess} is null ? {expressionForInit} : new {effectiveDisplayString}({expressionForMemberAccess});");
+                            _writer.WriteLine($"{memberAccessExpr} = {memberAccessExpr} is null ? {initExpr} : new {effectiveDisplayString}({memberAccessExpr});");
                         }
                         else
                         {
-                            _writer.WriteLine($"{expressionForMemberAccess} = {expressionForMemberAccess} is null ? {expressionForInit} : {expressionForMemberAccess}.{collectionType.ToEnumerableMethodCall!};");
+                            _writer.WriteLine($"{memberAccessExpr} = {memberAccessExpr} is null ? {initExpr} : {memberAccessExpr}.{collectionType.ToEnumerableMethodCall!};");
                         }
                     }
                     else
                     {
-                        _writer.WriteLine($"{expressionForMemberAccess} ??= {expressionForInit};");
+                        _writer.WriteLine($"{memberAccessExpr} ??= {initExpr};");
                     }
                 }
                 else
                 {
                     Debug.Assert(initKind is InitializationKind.SimpleAssignment);
-                    _writer.WriteLine($"{expressionForMemberAccess} = {expressionForInit};");
+                    _writer.WriteLine($"{memberAccessExpr} = {initExpr};");
                 }
 
                 return true;
             }
 
-            public void EmitCastToIConfigurationSection()
+            private void EmitCastToIConfigurationSection()
             {
                 string sectionTypeDisplayString;
                 string exceptionTypeDisplayString;
@@ -252,7 +230,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
                     """);
             }
 
-            public void EmitIConfigurationHasValueOrChildrenCheck(bool voidReturn)
+            private void EmitIConfigurationHasValueOrChildrenCheck(bool voidReturn)
             {
                 string returnPostfix = voidReturn ? string.Empty : " null";
                 string methodDisplayString = GetHelperMethodDisplayString(Identifier.HasValueOrChildren);
index 4174520..9127046 100644 (file)
@@ -99,18 +99,14 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
                 if (IsNullable(type, out ITypeSymbol? underlyingType))
                 {
                     spec = TryGetTypeSpec(underlyingType, Diagnostics.NullableUnderlyingTypeNotSupported, out TypeSpec? underlyingTypeSpec)
-                        ? new NullableSpec(type) { Location = location, UnderlyingType = underlyingTypeSpec }
+                        ? new NullableSpec(type, underlyingTypeSpec)
                         : null;
                 }
                 else if (IsParsableFromString(type, out StringParsableTypeKind specialTypeKind))
                 {
-                    ParsableFromStringSpec stringParsableSpec = new(type)
-                    {
-                        Location = location,
-                        StringParsableTypeKind = specialTypeKind
-                    };
+                    ParsableFromStringSpec stringParsableSpec = new(type) { StringParsableTypeKind = specialTypeKind };
 
-                    if (stringParsableSpec.StringParsableTypeKind is not StringParsableTypeKind.ConfigValue)
+                    if (stringParsableSpec.StringParsableTypeKind is not StringParsableTypeKind.AssignFromSectionValue)
                     {
                         _sourceGenSpec.PrimitivesForHelperGen.Add(stringParsableSpec);
                     }
@@ -119,17 +115,15 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
                 }
                 else if (IsSupportedArrayType(type, location))
                 {
-                    spec = CreateArraySpec((type as IArrayTypeSymbol)!, location);
-                    RegisterBindCoreGenType(spec);
+                    spec = CreateArraySpec((type as IArrayTypeSymbol));
                 }
                 else if (IsCollection(type))
                 {
                     spec = CreateCollectionSpec((INamedTypeSymbol)type, location);
-                    RegisterBindCoreGenType(spec);
                 }
                 else if (SymbolEqualityComparer.Default.Equals(type, _typeSymbols.IConfigurationSection))
                 {
-                    spec = new ConfigurationSectionSpec(type) { Location = location };
+                    spec = new ConfigurationSectionSpec(type);
                 }
                 else if (type is INamedTypeSymbol namedType)
                 {
@@ -138,7 +132,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
                     _sourceGenSpec.TypeNamespaces.Add("System.Collections.Generic");
 
                     spec = CreateObjectSpec(namedType, location);
-                    RegisterBindCoreGenType(spec);
                 }
 
                 if (spec is null)
@@ -154,14 +147,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
                 }
 
                 return _createdSpecs[type] = spec;
-
-                void RegisterBindCoreGenType(TypeSpec? spec)
-                {
-                    if (spec is not null)
-                    {
-                        RegisterTypeForMethodGen(MethodsToGen_CoreBindingHelper.BindCore, spec);
-                    }
-                }
             }
 
             private void RegisterTypeForMethodGen(MethodsToGen_CoreBindingHelper method, TypeSpec type)
@@ -177,8 +162,11 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
 
             private void RegisterTypeForBindCoreUntypedGen(TypeSpec typeSpec)
             {
-                RegisterTypeForMethodGen(MethodsToGen_CoreBindingHelper.BindCore, typeSpec);
-                RegisterTypeForMethodGen(MethodsToGen_CoreBindingHelper.BindCoreUntyped, typeSpec);
+                if (typeSpec.NeedsMemberBinding)
+                {
+                    RegisterTypeForMethodGen(MethodsToGen_CoreBindingHelper.BindCore, typeSpec);
+                    RegisterTypeForMethodGen(MethodsToGen_CoreBindingHelper.BindCoreUntyped, typeSpec);
+                }
             }
 
             private static bool IsNullable(ITypeSymbol type, [NotNullWhen(true)] out ITypeSymbol? underlyingType)
@@ -222,7 +210,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
                     case SpecialType.System_String:
                     case SpecialType.System_Object:
                         {
-                            typeKind = StringParsableTypeKind.ConfigValue;
+                            typeKind = StringParsableTypeKind.AssignFromSectionValue;
                             return true;
                         }
                     case SpecialType.System_Boolean:
@@ -312,7 +300,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
                 return true;
             }
 
-            private EnumerableSpec? CreateArraySpec(IArrayTypeSymbol arrayType, Location? location)
+            private EnumerableSpec? CreateArraySpec(IArrayTypeSymbol arrayType)
             {
                 if (!TryGetTypeSpec(arrayType.ElementType, Diagnostics.ElementTypeNotSupported, out TypeSpec elementSpec))
                 {
@@ -325,7 +313,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
 
                 EnumerableSpec spec = new EnumerableSpec(arrayType)
                 {
-                    Location = location,
                     ElementType = elementSpec,
                     ConcreteType = listSpec,
                     InitializationStrategy = InitializationStrategy.Array,
@@ -334,6 +321,8 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
                 };
 
                 Debug.Assert(spec.CanInitialize);
+                RegisterTypeForMethodGen(MethodsToGen_CoreBindingHelper.BindCore, spec);
+
                 return spec;
             }
 
@@ -368,6 +357,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
 
                 if (spec is not null)
                 {
+                    RegisterTypeForMethodGen(MethodsToGen_CoreBindingHelper.BindCore, spec);
                     spec.InitExceptionMessage ??= spec.ElementType.InitExceptionMessage;
                 }
 
@@ -436,7 +426,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
 
                 DictionarySpec spec = new(type)
                 {
-                    Location = location,
                     KeyType = (ParsableFromStringSpec)keySpec,
                     ElementType = elementSpec,
                     InitializationStrategy = constructionStrategy,
@@ -523,11 +512,10 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
                     return null;
                 }
 
-                RegisterHasChildrenHelperForGenIfRequired(elementSpec);
+                Register_AsConfigWithChildren_HelperForGen_IfRequired(elementSpec);
 
                 EnumerableSpec spec = new(type)
                 {
-                    Location = location,
                     ElementType = elementSpec,
                     InitializationStrategy = constructionStrategy,
                     PopulationStrategy = populationStrategy,
@@ -544,7 +532,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
             private ObjectSpec? CreateObjectSpec(INamedTypeSymbol type, Location? location)
             {
                 // Add spec to cache before traversing properties to avoid stack overflow.
-                ObjectSpec objectSpec = new(type) { Location = location };
+                ObjectSpec objectSpec = new(type);
                 _createdSpecs.Add(type, objectSpec);
 
                 string typeName = objectSpec.Name;
@@ -627,7 +615,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
                             {
                                 PropertySpec spec = new(property) { Type = propertyTypeSpec, ConfigurationKeyName = configKeyName };
                                 objectSpec.Properties[propertyName] = spec;
-                                RegisterHasChildrenHelperForGenIfRequired(propertyTypeSpec);
+                                Register_AsConfigWithChildren_HelperForGen_IfRequired(propertyTypeSpec);
                             }
                         }
                     }
@@ -691,17 +679,22 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
                 Debug.Assert((objectSpec.CanInitialize && objectSpec.InitExceptionMessage is null) ||
                     (!objectSpec.CanInitialize && objectSpec.InitExceptionMessage is not null));
 
+                if (objectSpec.NeedsMemberBinding)
+                {
+                    RegisterTypeForMethodGen(MethodsToGen_CoreBindingHelper.BindCore, objectSpec);
+                }
+
                 return objectSpec;
             }
 
-            private void RegisterHasChildrenHelperForGenIfRequired(TypeSpec type)
+            private void Register_AsConfigWithChildren_HelperForGen_IfRequired(TypeSpec type)
             {
                 if (type.SpecKind is TypeSpecKind.Object or
                                         TypeSpecKind.Enumerable or
                                         TypeSpecKind.Dictionary)
                 {
 
-                    _sourceGenSpec.ShouldEmitHasChildren = true;
+                    _sourceGenSpec.MethodsToGen_CoreBindingHelper |= MethodsToGen_CoreBindingHelper.AsConfigWithChildren;
                 }
             }
 
index a90eacd..d71e414 100644 (file)
@@ -24,21 +24,20 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
                     return;
                 }
 
-                _writer.WriteLine("/// <summary>Generated helper providing an AOT and linking compatible implementation for configuration binding.</summary>");
-                _writer.WriteBlockStart($"internal static class {Identifier.GeneratedConfigurationBinder}");
+                _emitBlankLineBeforeNextStatement = false;
+                EmitRootBindingClassBlockStart(Identifier.GeneratedConfigurationBinder);
 
                 EmitGetMethods();
                 EmitGetValueMethods();
                 EmitBindMethods_ConfigurationBinder();
 
                 _writer.WriteBlockEnd();
-
-                _precedingBlockExists = true;
+                _emitBlankLineBeforeNextStatement = true;
             }
 
             private void EmitGetMethods()
             {
-                const string expressionForGetCore = $"{FullyQualifiedDisplayString.CoreBindingHelper}.{Identifier.GetCore}";
+                const string expressionForGetCore = $"{FullyQualifiedDisplayString.CoreBindingHelper}.{nameof(MethodsToGen_CoreBindingHelper.GetCore)}";
                 const string documentation = "Attempts to bind the configuration instance to a new instance of type T.";
 
                 if (ShouldEmitMethods(MethodsToGen_ConfigurationBinder.Get_T))
@@ -72,7 +71,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
 
             private void EmitGetValueMethods()
             {
-                const string expressionForGetValueCore = $"{FullyQualifiedDisplayString.CoreBindingHelper}.{Identifier.GetValueCore}";
+                const string expressionForGetValueCore = $"{FullyQualifiedDisplayString.CoreBindingHelper}.{nameof(MethodsToGen_CoreBindingHelper.GetValueCore)}";
                 const string documentation = "Extracts the value with the specified key and converts it to the specified type.";
 
                 if (ShouldEmitMethods(MethodsToGen_ConfigurationBinder.GetValue_T_key))
@@ -144,7 +143,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
                         EmitMethodImplementation(
                             type,
                             additionalParams: $"string {Identifier.key}, {GetObjParameter(type)}",
-                            configExpression: $"{Identifier.configuration}.{Identifier.GetSection}({Identifier.key})",
+                            configExpression: $"{Expression.configurationGetSection}({Identifier.key})",
                             configureOptions: false);
                     }
                 }
@@ -152,9 +151,18 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
                 void EmitMethodImplementation(TypeSpec type, string additionalParams, string configExpression, bool configureOptions)
                 {
                     string binderOptionsArg = configureOptions ? $"{Expression.GetBinderOptions}({Identifier.configureOptions})" : $"{Identifier.binderOptions}: null";
-                    string returnExpression = type.CanInitialize
-                        ? $"{FullyQualifiedDisplayString.CoreBindingHelper}.{Identifier.BindCore}({configExpression}, ref {Identifier.obj}, {binderOptionsArg})"
-                        : GetInitException(type.InitExceptionMessage);
+
+                    string returnExpression;
+                    if (type.CanInitialize)
+                    {
+                        returnExpression = type.NeedsMemberBinding
+                            ? $"{FullyQualifiedDisplayString.CoreBindingHelper}.{nameof(MethodsToGen_CoreBindingHelper.BindCore)}({configExpression}, ref {Identifier.obj}, {binderOptionsArg})"
+                            : "{ }";
+                    }
+                    else
+                    {
+                        returnExpression = GetInitException(type.InitExceptionMessage);
+                    }
 
                     StartMethodDefinition("Attempts to bind the given object instance to configuration values by matching property names against configuration keys recursively.");
                     _writer.WriteLine($"public static void {Identifier.Bind}(this {FullyQualifiedDisplayString.IConfiguration} {Identifier.configuration}, {additionalParams}) => "
index 04b811c..bdf5606 100644 (file)
@@ -18,18 +18,23 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
 
             private void Emit_CoreBindingHelper()
             {
-                Debug.Assert(_precedingBlockExists);
+                Debug.Assert(_emitBlankLineBeforeNextStatement);
                 _writer.WriteBlankLine();
-                _precedingBlockExists = false;
+                _emitBlankLineBeforeNextStatement = false;
 
                 _writer.WriteBlockStart($"namespace {ProjectName}");
                 EmitHelperUsingStatements();
 
                 _writer.WriteBlankLine();
 
-                _writer.WriteLine("/// <summary>Provide core binding logic.</summary>");
-                _writer.WriteBlockStart($"internal static class {Identifier.CoreBindingHelper}");
+                _writer.WriteBlock($$"""
+                    /// <summary>Provide core binding logic.</summary>
+                    {{GetGeneratedCodeAttributeSrc()}}
+                    file static class {{Identifier.CoreBindingHelper}}
+                    {
+                    """);
 
+                EmitConfigurationKeyCaches();
                 EmitGetCoreMethod();
                 EmitGetValueCoreMethod();
                 EmitBindCoreUntypedMethod();
@@ -49,6 +54,32 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
                 }
             }
 
+            private void EmitConfigurationKeyCaches()
+            {
+                if (!_sourceGenSpec.TypesForGen_CoreBindingHelper_Methods.TryGetValue(MethodsToGen_CoreBindingHelper.BindCore, out HashSet<TypeSpec> targetTypes))
+                {
+                    return;
+                }
+
+                foreach (TypeSpec type in targetTypes)
+                {
+                    if (type is not ObjectSpec objectType)
+                    {
+                        continue;
+                    }
+
+                    HashSet<string> keys = new(objectType.ConstructorParameters.Select(m => GetCacheElement(m)));
+                    keys.UnionWith(objectType.Properties.Values.Select(m => GetCacheElement(m)));
+                    static string GetCacheElement(MemberSpec member) => $@"""{member.ConfigurationKeyName}""";
+
+                    string configKeysSource = string.Join(", ", keys);
+                    string fieldName = GetConfigKeyCacheFieldName(objectType);
+                    _writer.WriteLine($@"private readonly static Lazy<{MinimalDisplayString.HashSetOfString}> {fieldName} = new(() => new {MinimalDisplayString.HashSetOfString}(StringComparer.OrdinalIgnoreCase) {{ {configKeysSource} }});");
+                }
+
+                _emitBlankLineBeforeNextStatement = true;
+            }
+
             private void EmitGetCoreMethod()
             {
                 if (!_sourceGenSpec.TypesForGen_CoreBindingHelper_Methods.TryGetValue(MethodsToGen_CoreBindingHelper.GetCore, out HashSet<TypeSpec>? types))
@@ -56,7 +87,8 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
                     return;
                 }
 
-                _writer.WriteBlockStart($"public static object? {Identifier.GetCore}(this {Identifier.IConfiguration} {Identifier.configuration}, Type {Identifier.type}, Action<{Identifier.BinderOptions}>? {Identifier.configureOptions})");
+                EmitBlankLineIfRequired();
+                _writer.WriteBlockStart($"public static object? {nameof(MethodsToGen_CoreBindingHelper.GetCore)}(this {Identifier.IConfiguration} {Identifier.configuration}, Type {Identifier.type}, Action<{Identifier.BinderOptions}>? {Identifier.configureOptions})");
 
                 EmitCheckForNullArgument_WithBlankLine(Identifier.configuration);
 
@@ -67,11 +99,24 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
 
                 foreach (TypeSpec type in types)
                 {
+                    TypeSpecKind kind = type.SpecKind;
+
                     _writer.WriteBlockStart($"if (type == typeof({type.MinimalDisplayString}))");
 
-                    if (type.InitializationStrategy is InitializationStrategy.None || !EmitInitException(type))
+                    if (type is ParsableFromStringSpec stringParsableType)
                     {
-                        EmitBindLogicFromRootMethod(type, Identifier.obj, InitializationKind.Declaration);
+                        EmitCastToIConfigurationSection();
+                        EmitBindLogicFromString(
+                            stringParsableType,
+                            Expression.sectionValue,
+                            Expression.sectionPath,
+                            writeOnSuccess: parsedValueExpr => _writer.WriteLine($"return {parsedValueExpr};"),
+                            checkForNullSectionValue: stringParsableType.StringParsableTypeKind is not StringParsableTypeKind.AssignFromSectionValue,
+                            useIncrementalStringValueIdentifier: false);
+                    }
+                    else if (!EmitInitException(type))
+                    {
+                        EmitBindCoreCall(type, Identifier.obj, Identifier.configuration, InitializationKind.Declaration);
                         _writer.WriteLine($"return {Identifier.obj};");
                     }
 
@@ -81,7 +126,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
 
                 Emit_NotSupportedException_TypeNotDetectedAsInput();
                 _writer.WriteBlockEnd();
-                _precedingBlockExists = true;
+                _emitBlankLineBeforeNextStatement = true;
             }
 
             private void EmitGetValueCoreMethod()
@@ -92,25 +137,32 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
                 }
 
                 EmitBlankLineIfRequired();
-
-                _writer.WriteBlockStart($"public static object? {Identifier.GetValueCore}(this {Identifier.IConfiguration} {Identifier.configuration}, Type {Identifier.type}, string {Identifier.key})");
+                _writer.WriteBlockStart($"public static object? {nameof(MethodsToGen_CoreBindingHelper.GetValueCore)}(this {Identifier.IConfiguration} {Identifier.configuration}, Type {Identifier.type}, string {Identifier.key})");
 
                 EmitCheckForNullArgument_WithBlankLine(Identifier.configuration);
+                _writer.WriteLine($@"{Identifier.IConfigurationSection} {Identifier.section} = {GetSectionFromConfigurationExpression(Identifier.key, addQuotes: false)};");
+                _writer.WriteBlankLine();
 
-                _writer.WriteLine($"{Identifier.IConfigurationSection} {Identifier.section} = {Identifier.configuration}.{Identifier.GetSection}({Identifier.key});");
+                _writer.WriteBlock($$"""
+                    if ({{Expression.sectionValue}} is not string {{Identifier.value}})
+                    {
+                        return null;
+                    }
+                    """);
 
                 _writer.WriteBlankLine();
 
                 foreach (TypeSpec type in targetTypes)
                 {
-                    ParsableFromStringSpec effectiveType = (ParsableFromStringSpec)((type as NullableSpec)?.UnderlyingType ?? type);
                     _writer.WriteBlockStart($"if ({Identifier.type} == typeof({type.MinimalDisplayString}))");
 
                     EmitBindLogicFromString(
-                            effectiveType,
-                            Expression.sectionValue,
-                            Expression.sectionPath,
-                            writeOnSuccess: (parsedValueExpr) => _writer.WriteLine($"return {parsedValueExpr};"));
+                        (ParsableFromStringSpec)type.EffectiveType,
+                        Identifier.value,
+                        Expression.sectionPath,
+                        writeOnSuccess: (parsedValueExpr) => _writer.WriteLine($"return {parsedValueExpr};"),
+                        checkForNullSectionValue: false,
+                        useIncrementalStringValueIdentifier: false);
 
                     _writer.WriteBlockEnd();
                     _writer.WriteBlankLine();
@@ -118,7 +170,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
 
                 _writer.WriteLine("return null;");
                 _writer.WriteBlockEnd();
-                _precedingBlockExists = true;
+                _emitBlankLineBeforeNextStatement = true;
             }
 
             private void EmitBindCoreUntypedMethod()
@@ -130,7 +182,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
 
                 EmitBlankLineIfRequired();
 
-                _writer.WriteBlockStart($"public static void {Identifier.BindCoreUntyped}(this {Identifier.IConfiguration} {Identifier.configuration}, object {Identifier.obj}, Type {Identifier.type}, {MinimalDisplayString.NullableActionOfBinderOptions} {Identifier.configureOptions})");
+                _writer.WriteBlockStart($"public static void {nameof(MethodsToGen_CoreBindingHelper.BindCoreUntyped)}(this {Identifier.IConfiguration} {Identifier.configuration}, object {Identifier.obj}, Type {Identifier.type}, {MinimalDisplayString.NullableActionOfBinderOptions} {Identifier.configureOptions})");
 
                 EmitCheckForNullArgument_WithBlankLine(Identifier.configuration);
 
@@ -143,10 +195,11 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
                 {
                     _writer.WriteBlockStart($"if (type == typeof({type.MinimalDisplayString}))");
 
-                    if (type.InitializationStrategy is InitializationStrategy.None || !EmitInitException(type))
+                    TypeSpec effectiveType = type.EffectiveType;
+                    if (!EmitInitException(effectiveType))
                     {
-                        _writer.WriteLine($"var {Identifier.temp} = ({type.MinimalDisplayString}){Identifier.obj};");
-                        EmitBindLogicFromRootMethod(type, Identifier.temp, InitializationKind.None);
+                        _writer.WriteLine($"var {Identifier.temp} = ({effectiveType.MinimalDisplayString}){Identifier.obj};");
+                        EmitBindCoreCall(type, Identifier.temp, Identifier.configuration, InitializationKind.None);
                         _writer.WriteLine($"return;");
                     }
 
@@ -156,7 +209,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
 
                 Emit_NotSupportedException_TypeNotDetectedAsInput();
                 _writer.WriteBlockEnd();
-                _precedingBlockExists = true;
+                _emitBlankLineBeforeNextStatement = true;
             }
 
             private void EmitBindCoreMethods()
@@ -168,11 +221,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
 
                 foreach (TypeSpec type in targetTypes)
                 {
-                    if (type.SpecKind is TypeSpecKind.ParsableFromString)
-                    {
-                        continue;
-                    }
-
+                    Debug.Assert(type.NeedsMemberBinding);
                     EmitBlankLineIfRequired();
                     EmitBindCoreMethod(type);
                 }
@@ -180,14 +229,35 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
 
             private void EmitBindCoreMethod(TypeSpec type)
             {
-                if (!type.CanInitialize)
+                Debug.Assert(type.CanInitialize);
+
+                string objParameterExpression = $"ref {type.MinimalDisplayString} {Identifier.obj}";
+                _writer.WriteBlockStart(@$"public static void {nameof(MethodsToGen_CoreBindingHelper.BindCore)}({Identifier.IConfiguration} {Identifier.configuration}, {objParameterExpression}, {Identifier.BinderOptions}? {Identifier.binderOptions})");
+
+                EmitCheckForNullArgument_WithBlankLine_IfRequired(type.IsValueType);
+
+                TypeSpec effectiveType = type.EffectiveType;
+                if (effectiveType is EnumerableSpec enumerable)
                 {
-                    return;
+                    if (effectiveType.InitializationStrategy is InitializationStrategy.Array)
+                    {
+                        Debug.Assert(type == effectiveType);
+                        EmitPopulationImplForArray((EnumerableSpec)type);
+                    }
+                    else
+                    {
+                        EmitPopulationImplForEnumerableWithAdd(enumerable);
+                    }
+                }
+                else if (effectiveType is DictionarySpec dictionary)
+                {
+                    EmitBindCoreImplForDictionary(dictionary);
+                }
+                else
+                {
+                    EmitBindCoreImplForObject((ObjectSpec)effectiveType);
                 }
 
-                string objParameterExpression = $"ref {type.MinimalDisplayString} {Identifier.obj}";
-                _writer.WriteBlockStart(@$"public static void {Identifier.BindCore}({Identifier.IConfiguration} {Identifier.configuration}, {objParameterExpression}, {Identifier.BinderOptions}? {Identifier.binderOptions})");
-                EmitBindCoreImpl(type);
                 _writer.WriteBlockEnd();
             }
 
@@ -208,84 +278,39 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
             private void EmitInitializeMethod(ObjectSpec type)
             {
                 Debug.Assert(type.CanInitialize);
-
                 List<ParameterSpec> ctorParams = type.ConstructorParameters;
-                IEnumerable<PropertySpec> initOnlyProps = type.Properties.Values.Where(prop => prop.SetOnInit);
+                IEnumerable<PropertySpec> initOnlyProps = type.Properties.Values.Where(prop => prop is { SetOnInit: true });
+                List<string> ctorArgList = new();
                 string displayString = type.MinimalDisplayString;
 
-                _writer.WriteBlockStart($"public static {displayString} {type.InitializeMethodDisplayString}({Identifier.IConfiguration} {Identifier.configuration}, {Identifier.BinderOptions}? {Identifier.binderOptions})");
+                _writer.WriteBlockStart($"public static {type.MinimalDisplayString} {GetInitalizeMethodDisplayString(type)}({Identifier.IConfiguration} {Identifier.configuration}, {Identifier.BinderOptions}? {Identifier.binderOptions})");
+                _emitBlankLineBeforeNextStatement = false;
 
                 foreach (ParameterSpec parameter in ctorParams)
                 {
-                    if (!parameter.HasExplicitDefaultValue)
+                    string name = parameter.Name;
+                    string argExpr = parameter.RefKind switch
                     {
-                        _writer.WriteLine($@"({parameter.Type.MinimalDisplayString} {Identifier.Value}, bool {Identifier.HasConfig}) {parameter.Name} = ({parameter.DefaultValue}, false);");
-                    }
-                    else
-                    {
-                        _writer.WriteLine($@"{parameter.Type.MinimalDisplayString} {parameter.Name} = {parameter.DefaultValue};");
-                    }
-                }
-
-                foreach (PropertySpec property in initOnlyProps)
-                {
-                    if (property.MatchingCtorParam is null)
-                    {
-                        _writer.WriteLine($@"{property.Type.MinimalDisplayString} {property.Name} = default!;");
-                    }
-                }
-
-                _writer.WriteBlankLine();
-
-                _writer.WriteBlock($$"""
-                        foreach ({{Identifier.IConfigurationSection}} {{Identifier.section}} in {{Identifier.configuration}}.{{Identifier.GetChildren}}())
-                        {
-                            switch ({{Expression.sectionKey}})
-                            {
-                    """);
-
-                List<string> argumentList = new();
+                        RefKind.None => name,
+                        RefKind.Ref => $"ref {name}",
+                        RefKind.Out => "out _",
+                        RefKind.In => $"in {name}",
+                        _ => throw new InvalidOperationException()
+                    };
 
-                foreach (ParameterSpec parameter in ctorParams)
-                {
-                    EmitMemberBindLogic(parameter.Name, parameter.Type, parameter.ConfigurationKeyName, configValueMustExist: !parameter.HasExplicitDefaultValue);
-                    argumentList.Add(GetExpressionForArgument(parameter));
+                    ctorArgList.Add(argExpr);
+                    EmitBindImplForMember(parameter);
                 }
 
                 foreach (PropertySpec property in initOnlyProps)
                 {
                     if (property.ShouldBind() && property.MatchingCtorParam is null)
                     {
-                        EmitMemberBindLogic(property.Name, property.Type, property.ConfigurationKeyName);
+                        EmitBindImplForMember(property);
                     }
                 }
 
-                EmitSwitchDefault("continue;", addBreak: false);
-
-                _writer.WriteBlockEnd();
-                _writer.WriteBlockEnd();
-
-                _precedingBlockExists = true;
-
-                foreach (ParameterSpec parameter in ctorParams)
-                {
-                    if (!parameter.HasExplicitDefaultValue)
-                    {
-                        string parameterName = parameter.Name;
-
-                        EmitBlankLineIfRequired();
-                        _writer.WriteBlock($$"""
-                        if (!{{parameterName}}.{{Identifier.HasConfig}})
-                        {
-                            throw new {{GetInvalidOperationDisplayName()}}("{{string.Format(ExceptionMessages.ParameterHasNoMatchingConfig, type.Name, parameterName)}}");
-                        }
-                        """);
-                    }
-                }
-
-                EmitBlankLineIfRequired();
-
-                string returnExpression = $"return new {displayString}({string.Join(", ", argumentList)})";
+                string returnExpression = $"return new {displayString}({string.Join(", ", ctorArgList)})";
                 if (!initOnlyProps.Any())
                 {
                     _writer.WriteLine($"{returnExpression};");
@@ -296,87 +321,104 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
                     foreach (PropertySpec property in initOnlyProps)
                     {
                         string propertyName = property.Name;
-                        string initValue = propertyName + (property.MatchingCtorParam is null or ParameterSpec { HasExplicitDefaultValue: true } ? string.Empty : $".{Identifier.Value}");
-                        _writer.WriteLine($@"{propertyName} = {initValue},");
+                        _writer.WriteLine($@"{propertyName} = {propertyName},");
                     }
                     _writer.WriteBlockEnd(";");
                 }
 
                 // End method.
                 _writer.WriteBlockEnd();
+                _emitBlankLineBeforeNextStatement = true;
 
-                void EmitMemberBindLogic(string memberName, TypeSpec memberType, string configurationKeyName, bool configValueMustExist = false)
+                void EmitBindImplForMember(MemberSpec member)
                 {
-                    string lhs = memberName + (configValueMustExist ? $".{Identifier.Value}" : string.Empty);
-
-                    _writer.WriteLine($@"case ""{configurationKeyName}"":");
-                    _writer.Indentation++;
-                    _writer.WriteBlockStart();
+                    TypeSpec memberType = member.Type;
+                    bool errorOnFailedBinding = member.ErrorOnFailedBinding;
 
-                    EmitMemberBindLogicCore(memberType, lhs);
+                    string parsedMemberIdentifierDeclarationPrefix = $"{memberType.MinimalDisplayString} {member.Name}";
+                    string parsedMemberIdentifier;
 
-                    if (configValueMustExist)
+                    if (memberType is ParsableFromStringSpec { StringParsableTypeKind: StringParsableTypeKind.AssignFromSectionValue })
                     {
-                        _writer.WriteLine($"{memberName}.{Identifier.HasConfig} = true;");
-                    }
-
-                    _writer.WriteBlockEnd();
-                    _writer.WriteLine("break;");
-                    _writer.Indentation--;
-
-                    void EmitMemberBindLogicCore(TypeSpec type, string lhs)
-                    {
-                        TypeSpecKind kind = type.SpecKind;
+                        parsedMemberIdentifier = parsedMemberIdentifierDeclarationPrefix;
 
-                        if (kind is TypeSpecKind.Nullable)
+                        if (errorOnFailedBinding)
                         {
-                            EmitMemberBindLogicCore(((NullableSpec)type).UnderlyingType, lhs);
+                            string condition = $@" if ({Identifier.configuration}[""{member.ConfigurationKeyName}""] is not {memberType.MinimalDisplayString} {member.Name})";
+                            EmitThrowBlock(condition);
+                            _writer.WriteBlankLine();
+                            return;
                         }
-                        else if (type is ParsableFromStringSpec stringParsableType)
+                    }
+                    else
+                    {
+                        parsedMemberIdentifier = member.Name;
+
+                        string declarationSuffix;
+                        if (errorOnFailedBinding)
                         {
-                            EmitBindLogicFromString(
-                                stringParsableType,
-                                Expression.sectionValue,
-                                Expression.sectionPath,
-                                (parsedValueExpr) => _writer.WriteLine($"{lhs} = {parsedValueExpr}!;"));
+                            declarationSuffix = ";";
                         }
-                        else if (!EmitInitException(type))
+                        else
                         {
-                            EmitBindCoreCall(type, lhs, Identifier.section, InitializationKind.SimpleAssignment);
+                            string bangExpr = memberType.IsValueType ? string.Empty : "!";
+                            declarationSuffix = memberType.CanInitialize
+                                ? $" = {member.DefaultValueExpr}{bangExpr};"
+                                : ";";
                         }
+
+                        string parsedMemberIdentifierDeclaration = $"{parsedMemberIdentifierDeclarationPrefix}{declarationSuffix}";
+                        _writer.WriteLine(parsedMemberIdentifierDeclaration);
+                        _emitBlankLineBeforeNextStatement = false;
                     }
-                }
 
-                static string GetExpressionForArgument(ParameterSpec parameter)
-                {
-                    string name = parameter.Name + (parameter.HasExplicitDefaultValue ? string.Empty : $".{Identifier.Value}");
+                    bool canBindToMember = this.EmitBindImplForMember(
+                        member,
+                        parsedMemberIdentifier,
+                        sectionPathExpr: GetSectionPathFromConfigurationExpression(member.ConfigurationKeyName),
+                        canSet: true);
 
-                    return parameter.RefKind switch
+                    if (canBindToMember)
                     {
-                        RefKind.None => name,
-                        RefKind.Ref => $"ref {name}",
-                        RefKind.Out => "out _",
-                        RefKind.In => $"in {name}",
-                        _ => throw new InvalidOperationException()
-                    };
+                        if (errorOnFailedBinding)
+                        {
+                            // Add exception logic for parameter ctors; must be present in configuration object.
+                            EmitThrowBlock(condition: "else");
+                        }
+
+                        _writer.WriteBlankLine();
+                    }
+
+                    void EmitThrowBlock(string condition) =>
+                        _writer.WriteBlock($$"""
+                            {{condition}}
+                            {
+                                throw new {{GetInvalidOperationDisplayName()}}("{{string.Format(ExceptionMessages.ParameterHasNoMatchingConfig, type.Name, member.Name)}}");
+                            }
+                            """);
                 }
             }
 
             private void EmitHelperMethods()
             {
+                if (ShouldEmitMethods(MethodsToGen_CoreBindingHelper.BindCore))
+                {
+                    EmitValidateConfigurationKeysMethod();
+                }
+
                 if (ShouldEmitMethods(MethodsToGen_CoreBindingHelper.BindCoreUntyped | MethodsToGen_CoreBindingHelper.GetCore))
                 {
                     _writer.WriteBlankLine();
                     EmitHasValueOrChildrenMethod();
                     _writer.WriteBlankLine();
-                    EmitHasChildrenMethod();
-                    _precedingBlockExists = true;
+                    EmitAsConfigWithChildrenMethod();
+                    _emitBlankLineBeforeNextStatement = true;
                 }
-                else if (_sourceGenSpec.ShouldEmitHasChildren)
+                else if (ShouldEmitMethods(MethodsToGen_CoreBindingHelper.AsConfigWithChildren))
                 {
                     _writer.WriteBlankLine();
-                    EmitHasChildrenMethod();
-                    _precedingBlockExists = true;
+                    EmitAsConfigWithChildrenMethod();
+                    _emitBlankLineBeforeNextStatement = true;
                 }
 
                 if (ShouldEmitMethods(
@@ -385,7 +427,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
                 {
                     _writer.WriteBlankLine();
                     EmitGetBinderOptionsHelper();
-                    _precedingBlockExists = true;
+                    _emitBlankLineBeforeNextStatement = true;
                 }
 
                 foreach (ParsableFromStringSpec type in _sourceGenSpec.PrimitivesForHelperGen)
@@ -395,6 +437,37 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
                 }
             }
 
+            private void EmitValidateConfigurationKeysMethod()
+            {
+                const string keysIdentifier = "keys";
+                string exceptionMessage = string.Format(ExceptionMessages.MissingConfig, Identifier.ErrorOnUnknownConfiguration, Identifier.BinderOptions, $"{{{Identifier.type}}}", $@"{{string.Join("", "", {Identifier.temp})}}");
+
+                EmitBlankLineIfRequired();
+                _writer.WriteBlock($$"""
+                    /// <summary>If required by the binder options, validates that there are no unknown keys in the input configuration object.</summary>
+                    public static void {{Identifier.ValidateConfigurationKeys}}(Type {{Identifier.type}}, {{MinimalDisplayString.LazyHashSetOfString}} {{keysIdentifier}}, {{Identifier.IConfiguration}} {{Identifier.configuration}}, {{Identifier.BinderOptions}}? {{Identifier.binderOptions}})
+                    {
+                        if ({{Identifier.binderOptions}}?.{{Identifier.ErrorOnUnknownConfiguration}} is true)
+                        {
+                            {{MinimalDisplayString.ListOfString}}? {{Identifier.temp}} = null;
+                    
+                            foreach ({{Identifier.IConfigurationSection}} {{Identifier.section}} in {{Identifier.configuration}}.{{Identifier.GetChildren}}())
+                            {
+                                if (!{{keysIdentifier}}.Value.Contains({{Expression.sectionKey}}))
+                                {
+                                    ({{Identifier.temp}} ??= new {{MinimalDisplayString.ListOfString}}()).Add($"'{{{Expression.sectionKey}}}'");
+                                }
+                            }
+
+                            if ({{Identifier.temp}} is not null)
+                            {
+                                throw new InvalidOperationException($"{{exceptionMessage}}");
+                            }
+                        }
+                    }
+                    """);
+            }
+
             private void EmitHasValueOrChildrenMethod()
             {
                 _writer.WriteBlock($$"""
@@ -404,21 +477,21 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
                         {
                             return true;
                         }
-                        return {{Identifier.HasChildren}}({{Identifier.configuration}});
+                        return {{Identifier.AsConfigWithChildren}}({{Identifier.configuration}}) is not null;
                     }
                     """);
             }
 
-            private void EmitHasChildrenMethod()
+            private void EmitAsConfigWithChildrenMethod()
             {
                 _writer.WriteBlock($$"""
-                    public static bool {{Identifier.HasChildren}}({{Identifier.IConfiguration}} {{Identifier.configuration}})
+                    public static {{Identifier.IConfiguration}}? {{Identifier.AsConfigWithChildren}}({{Identifier.IConfiguration}} {{Identifier.configuration}})
                     {
-                        foreach ({{Identifier.IConfigurationSection}} {{Identifier.section}} in {{Identifier.configuration}}.{{Identifier.GetChildren}}())
+                        foreach ({{Identifier.IConfigurationSection}} _ in {{Identifier.configuration}}.{{Identifier.GetChildren}}())
                         {
-                            return true;
+                            return {{Identifier.configuration}};
                         }
-                        return false;
+                        return null;
                     }
                     """);
             }
@@ -465,52 +538,52 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
                     numberStylesTypeDisplayString = "NumberStyles";
                 }
 
-                string invariantCultureExpression = $"{cultureInfoTypeDisplayString}.InvariantCulture";
-
-                string expressionForParsedValue;
                 StringParsableTypeKind typeKind = type.StringParsableTypeKind;
                 string typeDisplayString = type.MinimalDisplayString;
 
+                string invariantCultureExpression = $"{cultureInfoTypeDisplayString}.InvariantCulture";
+
+                string parsedValueExpr;
                 switch (typeKind)
                 {
                     case StringParsableTypeKind.Enum:
                         {
-                            expressionForParsedValue = $"({typeDisplayString}){Identifier.Enum}.{Identifier.Parse}(typeof({typeDisplayString}), {Identifier.stringValue}, ignoreCase: true)";
+                            parsedValueExpr = $"({typeDisplayString}){Identifier.Enum}.{Identifier.Parse}(typeof({typeDisplayString}), {Identifier.value}, ignoreCase: true)";
                         }
                         break;
                     case StringParsableTypeKind.ByteArray:
                         {
-                            expressionForParsedValue = $"Convert.FromBase64String({Identifier.stringValue})";
+                            parsedValueExpr = $"Convert.FromBase64String({Identifier.value})";
                         }
                         break;
                     case StringParsableTypeKind.Integer:
                         {
-                            expressionForParsedValue = $"{typeDisplayString}.{Identifier.Parse}({Identifier.stringValue}, {numberStylesTypeDisplayString}.Integer, {invariantCultureExpression})";
+                            parsedValueExpr = $"{typeDisplayString}.{Identifier.Parse}({Identifier.value}, {numberStylesTypeDisplayString}.Integer, {invariantCultureExpression})";
                         }
                         break;
                     case StringParsableTypeKind.Float:
                         {
-                            expressionForParsedValue = $"{typeDisplayString}.{Identifier.Parse}({Identifier.stringValue}, {numberStylesTypeDisplayString}.Float, {invariantCultureExpression})";
+                            parsedValueExpr = $"{typeDisplayString}.{Identifier.Parse}({Identifier.value}, {numberStylesTypeDisplayString}.Float, {invariantCultureExpression})";
                         }
                         break;
                     case StringParsableTypeKind.Parse:
                         {
-                            expressionForParsedValue = $"{typeDisplayString}.{Identifier.Parse}({Identifier.stringValue})";
+                            parsedValueExpr = $"{typeDisplayString}.{Identifier.Parse}({Identifier.value})";
                         }
                         break;
                     case StringParsableTypeKind.ParseInvariant:
                         {
-                            expressionForParsedValue = $"{typeDisplayString}.{Identifier.Parse}({Identifier.stringValue}, {invariantCultureExpression})"; ;
+                            parsedValueExpr = $"{typeDisplayString}.{Identifier.Parse}({Identifier.value}, {invariantCultureExpression})"; ;
                         }
                         break;
                     case StringParsableTypeKind.CultureInfo:
                         {
-                            expressionForParsedValue = $"{cultureInfoTypeDisplayString}.GetCultureInfo({Identifier.stringValue})";
+                            parsedValueExpr = $"{cultureInfoTypeDisplayString}.GetCultureInfo({Identifier.value})";
                         }
                         break;
                     case StringParsableTypeKind.Uri:
                         {
-                            expressionForParsedValue = $"new Uri({Identifier.stringValue}, UriKind.RelativeOrAbsolute)";
+                            parsedValueExpr = $"new Uri({Identifier.value}, UriKind.RelativeOrAbsolute)";
                         }
                         break;
                     default:
@@ -521,11 +594,11 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
                 }
 
                 _writer.WriteBlock($$"""
-                    public static {{typeDisplayString}} {{type.ParseMethodName}}(string {{Identifier.stringValue}}, Func<string?> {{Identifier.getPath}})
+                    public static {{typeDisplayString}} {{type.ParseMethodName}}(string {{Identifier.value}}, Func<string?> {{Identifier.getPath}})
                     {
                         try
                         {
-                            return {{expressionForParsedValue}};
+                            return {{parsedValueExpr}};
                     """);
 
                 string exceptionArg1 = string.Format(ExceptionMessages.FailedBinding, $"{{{Identifier.getPath}()}}", $"{{typeof({typeDisplayString})}}");
@@ -540,69 +613,19 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
                     """);
             }
 
-            private void EmitBindCoreImpl(TypeSpec type)
-            {
-                switch (type.SpecKind)
-                {
-                    case TypeSpecKind.Enumerable:
-                    case TypeSpecKind.Dictionary:
-                    case TypeSpecKind.Object:
-                        {
-                            Debug.Assert(type.CanInitialize);
-                            EmitCheckForNullArgument_WithBlankLine_IfRequired(type.IsValueType);
-                            EmitBindCoreImplForComplexType(type);
-                        }
-                        break;
-                    case TypeSpecKind.Nullable:
-                        {
-                            EmitBindCoreImpl(((NullableSpec)type).UnderlyingType);
-                        }
-                        break;
-                    case TypeSpecKind.IConfigurationSection:
-                        {
-                            EmitCastToIConfigurationSection();
-                            _writer.WriteLine($"{Identifier.obj} = {Identifier.section};");
-                        }
-                        break;
-                    default:
-                        Debug.Fail("Invalid type kind", type.SpecKind.ToString());
-                        break;
-                }
-            }
-
-            private void EmitBindCoreImplForComplexType(TypeSpec type)
-            {
-                if (type.InitializationStrategy is InitializationStrategy.Array)
-                {
-                    EmitPopulationImplForArray((EnumerableSpec)type);
-                }
-                else if (type is EnumerableSpec enumerable)
-                {
-                    EmitPopulationImplForEnumerableWithAdd(enumerable);
-                }
-                else if (type is DictionarySpec dictionary)
-                {
-                    EmitBindCoreImplForDictionary(dictionary);
-                }
-                else
-                {
-                    EmitBindCoreImplForObject((ObjectSpec)type);
-                }
-            }
-
             private void EmitPopulationImplForArray(EnumerableSpec type)
             {
                 EnumerableSpec concreteType = (EnumerableSpec)type.ConcreteType;
 
-                // Create, bind, and add elements to temp list.
-                string tempVarName = GetIncrementalVarName(Identifier.temp);
-                EmitBindCoreCall(concreteType, tempVarName, Identifier.configuration, InitializationKind.Declaration);
+                // Create list and bind elements.
+                string tempIdentifier = GetIncrementalIdentifier(Identifier.temp);
+                EmitBindCoreCall(concreteType, tempIdentifier, Identifier.configuration, InitializationKind.Declaration);
 
-                // Resize array and copy additional elements.
+                // Resize array and add binded elements.
                 _writer.WriteBlock($$"""
                     {{Identifier.Int32}} {{Identifier.originalCount}} = {{Identifier.obj}}.{{Identifier.Length}};
-                    {{Identifier.Array}}.{{Identifier.Resize}}(ref {{Identifier.obj}}, {{Identifier.originalCount}} + {{tempVarName}}.{{Identifier.Count}});
-                    {{tempVarName}}.{{Identifier.CopyTo}}({{Identifier.obj}}, {{Identifier.originalCount}});
+                    {{Identifier.Array}}.{{Identifier.Resize}}(ref {{Identifier.obj}}, {{Identifier.originalCount}} + {{tempIdentifier}}.{{Identifier.Count}});
+                    {{tempIdentifier}}.{{Identifier.CopyTo}}({{Identifier.obj}}, {{Identifier.originalCount}});
                     """);
             }
 
@@ -610,7 +633,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
             {
                 EmitCollectionCastIfRequired(type, out string objIdentifier);
 
-                _writer.WriteBlockStart($"foreach ({Identifier.IConfigurationSection} {Identifier.section} in {Identifier.configuration}.{Identifier.GetChildren}())");
+                Emit_Foreach_Section_In_ConfigChildren_BlockHeader();
 
                 TypeSpec elementType = type.ElementType;
 
@@ -620,13 +643,14 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
                         stringParsableType,
                         Expression.sectionValue,
                         Expression.sectionPath,
-                        (parsedValueExpr) => _writer.WriteLine($"{objIdentifier}.{Identifier.Add}({parsedValueExpr}!);"),
-                        isCollectionElement: true);
+                        (parsedValueExpr) => _writer.WriteLine($"{objIdentifier}.{Identifier.Add}({parsedValueExpr});"),
+                        checkForNullSectionValue: true,
+                        useIncrementalStringValueIdentifier: false);
                 }
                 else
                 {
-                    EmitBindCoreCall(elementType, Identifier.element, Identifier.section, InitializationKind.Declaration);
-                    _writer.WriteLine($"{objIdentifier}.{Identifier.Add}({Identifier.element});");
+                    EmitBindCoreCall(elementType, Identifier.value, Identifier.section, InitializationKind.Declaration);
+                    _writer.WriteLine($"{objIdentifier}.{Identifier.Add}({Identifier.value});");
                 }
 
                 _writer.WriteBlockEnd();
@@ -636,17 +660,19 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
             {
                 EmitCollectionCastIfRequired(type, out string objIdentifier);
 
-                _writer.WriteBlockStart($"foreach ({Identifier.IConfigurationSection} {Identifier.section} in {Identifier.configuration}.{Identifier.GetChildren}())");
+                Emit_Foreach_Section_In_ConfigChildren_BlockHeader();
 
                 ParsableFromStringSpec keyType = type.KeyType;
                 TypeSpec elementType = type.ElementType;
 
                 // Parse key
                 EmitBindLogicFromString(
-                        keyType,
-                        Expression.sectionKey,
-                        Expression.sectionPath,
-                        Emit_BindAndAddLogic_ForElement);
+                    keyType,
+                    Expression.sectionKey,
+                    Expression.sectionPath,
+                    Emit_BindAndAddLogic_ForElement,
+                    checkForNullSectionValue: false,
+                    useIncrementalStringValueIdentifier: false);
 
                 void Emit_BindAndAddLogic_ForElement(string parsedKeyExpr)
                 {
@@ -656,15 +682,15 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
                             stringParsableElementType,
                             Expression.sectionValue,
                             Expression.sectionPath,
-                            (parsedValueExpr) => _writer.WriteLine($"{objIdentifier}[{parsedKeyExpr}!] = {parsedValueExpr}!;"),
-                            isCollectionElement: true);
+                            writeOnSuccess: parsedValueExpr => _writer.WriteLine($"{objIdentifier}[{parsedKeyExpr}] = {parsedValueExpr};"),
+                            checkForNullSectionValue: true,
+                            useIncrementalStringValueIdentifier: false);
                     }
                     else // For complex types:
                     {
                         Debug.Assert(elementType.CanInitialize);
 
-                        parsedKeyExpr += "!";
-                        if (keyType.StringParsableTypeKind is not StringParsableTypeKind.ConfigValue)
+                        if (keyType.StringParsableTypeKind is not StringParsableTypeKind.AssignFromSectionValue)
                         {
                             // Save value to local to avoid parsing twice - during look-up and during add.
                             _writer.WriteLine($"{keyType.MinimalDisplayString} {Identifier.key} = {parsedKeyExpr};");
@@ -685,7 +711,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
                         }
 
                         _writer.WriteBlockStart($"if (!({conditionToUseExistingElement}))");
-                        EmitObjectInit(elementType, Identifier.element, InitializationKind.SimpleAssignment);
+                        EmitObjectInit(elementType, Identifier.element, InitializationKind.SimpleAssignment, Identifier.section);
                         _writer.WriteBlockEnd();
 
                         if (elementType is CollectionSpec { InitializationStrategy: InitializationStrategy.ParameterizedConstructor or InitializationStrategy.ToEnumerableMethod } collectionSpec)
@@ -716,175 +742,152 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
 
             private void EmitBindCoreImplForObject(ObjectSpec type)
             {
-                if (type.Properties.Count == 0)
-                {
-                    return;
-                }
+                Debug.Assert(type.NeedsMemberBinding);
 
-                string listOfStringDisplayName = "List<string>";
-                _writer.WriteLine($"{listOfStringDisplayName}? {Identifier.temp} = null;");
-
-                _writer.WriteBlockStart($"foreach ({Identifier.IConfigurationSection} {Identifier.section} in {Identifier.configuration}.{Identifier.GetChildren}())");
-                _writer.WriteBlockStart($"switch ({Expression.sectionKey})");
+                string keyCacheFieldName = GetConfigKeyCacheFieldName(type);
+                string validateMethodCallExpr = $"{Identifier.ValidateConfigurationKeys}(typeof({type.MinimalDisplayString}), {keyCacheFieldName}, {Identifier.configuration}, {Identifier.binderOptions});";
+                _writer.WriteLine(validateMethodCallExpr);
 
                 foreach (PropertySpec property in type.Properties.Values)
                 {
-                    _writer.WriteLine($@"case ""{property.ConfigurationKeyName}"":");
-                    _writer.Indentation++;
-                    _writer.WriteBlockStart();
-
-                    bool success = true;
-                    if (property.ShouldBind())
+                    bool noSetter_And_IsReadonly = !property.CanSet && property.Type is CollectionSpec { InitializationStrategy: InitializationStrategy.ParameterizedConstructor };
+                    if (property.ShouldBind() && !noSetter_And_IsReadonly)
                     {
-                        success = EmitBindCoreImplForProperty(property, property.Type, parentType: type);
+                        string containingTypeRef = property.IsStatic ? type.MinimalDisplayString : Identifier.obj;
+                        EmitBindImplForMember(
+                            property,
+                            memberAccessExpr: $"{containingTypeRef}.{property.Name}",
+                            GetSectionPathFromConfigurationExpression(property.ConfigurationKeyName),
+                            canSet: property.CanSet);
                     }
+                }
+            }
 
-                    _writer.WriteBlockEnd();
+            private bool EmitBindImplForMember(
+                MemberSpec member,
+                string memberAccessExpr,
+                string sectionPathExpr,
+                bool canSet)
+            {
+                TypeSpec effectiveMemberType = member.Type.EffectiveType;
 
-                    if (success)
+                if (effectiveMemberType is ParsableFromStringSpec stringParsableType)
+                {
+                    if (canSet)
                     {
-                        _writer.WriteLine("break;");
-                    }
+                        bool checkForNullSectionValue = member is ParameterSpec
+                            ? true
+                            : stringParsableType.StringParsableTypeKind is not StringParsableTypeKind.AssignFromSectionValue;
 
-                    _writer.Indentation--;
-                }
-
-                EmitSwitchDefault($$"""
-                if ({{Identifier.binderOptions}}?.ErrorOnUnknownConfiguration == true)
-                {
-                    ({{Identifier.temp}} ??= new {{listOfStringDisplayName}}()).Add($"'{{{Expression.sectionKey}}}'");
-                }
-                """);
+                        string nullBangExpr = checkForNullSectionValue ? string.Empty : "!";
 
-                // End switch on config child key.
-                _writer.WriteBlockEnd();
+                        EmitBlankLineIfRequired();
+                        EmitBindLogicFromString(
+                            stringParsableType,
+                            $@"{Identifier.configuration}[""{member.ConfigurationKeyName}""]",
+                            sectionPathExpr,
+                            writeOnSuccess: parsedValueExpr => _writer.WriteLine($"{memberAccessExpr} = {parsedValueExpr}{nullBangExpr};"),
+                            checkForNullSectionValue,
+                            useIncrementalStringValueIdentifier: true);
+                    }
 
-                // End foreach on config.GetChildren().
-                _writer.WriteBlockEnd();
+                    return true;
+                }
 
-                _writer.WriteBlankLine();
+                string sectionParseExpr = $"{GetSectionFromConfigurationExpression(member.ConfigurationKeyName)}";
 
-                string exceptionMessage = string.Format(ExceptionMessages.MissingConfig, Identifier.ErrorOnUnknownConfiguration, Identifier.BinderOptions, $"{{typeof({type.MinimalDisplayString})}}", $@"{{string.Join("", "", {Identifier.temp})}}");
-                _writer.WriteBlock($$"""
-                    if ({{Identifier.temp}} is not null)
-                    {
-                        throw new InvalidOperationException($"{{exceptionMessage}}");
-                    }
-                    """);
+                EmitBlankLineIfRequired();
 
-            }
+                if (effectiveMemberType.SpecKind is TypeSpecKind.IConfigurationSection)
+                {
+                    _writer.WriteLine($"{memberAccessExpr} = {sectionParseExpr};");
+                    return true;
+                }
 
-            private bool EmitBindCoreImplForProperty(PropertySpec property, TypeSpec propertyType, TypeSpec parentType)
-            {
-                string configurationKeyName = property.ConfigurationKeyName;
-                string propertyParentReference = property.IsStatic ? parentType.MinimalDisplayString : Identifier.obj;
-                string expressionForPropertyAccess = $"{propertyParentReference}.{property.Name}";
-                string expressionForConfigValueIndexer = $@"{Identifier.configuration}[""{configurationKeyName}""]";
+                string sectionValidationCall = $"{Identifier.AsConfigWithChildren}({sectionParseExpr})";
+                string sectionIdentifier = GetIncrementalIdentifier(Identifier.section);
 
-                bool canSet = property.CanSet;
+                _writer.WriteBlockStart($"if ({sectionValidationCall} is {Identifier.IConfigurationSection} {sectionIdentifier})");
 
-                switch (propertyType.SpecKind)
+                bool success = !EmitInitException(effectiveMemberType);
+                if (success)
                 {
-                    case TypeSpecKind.ParsableFromString:
-                        {
-                            if (canSet && propertyType is ParsableFromStringSpec stringParsableType)
-                            {
-                                EmitBindLogicFromString(
-                                    stringParsableType,
-                                    expressionForConfigValueIndexer,
-                                    Expression.sectionPath,
-                                    (parsedValueExpr) => _writer.WriteLine($"{expressionForPropertyAccess} = {parsedValueExpr}!;"));
-                            }
-                        }
-                        break;
-                    case TypeSpecKind.IConfigurationSection:
-                        {
-                            _writer.WriteLine($"{expressionForPropertyAccess} = {Identifier.section};");
-                        }
-                        break;
-                    case TypeSpecKind.Nullable:
-                        {
-                            TypeSpec underlyingType = ((NullableSpec)propertyType).UnderlyingType;
-                            EmitBindCoreImplForProperty(property, underlyingType, parentType);
-                        }
-                        break;
-                    default:
-                        {
-                            if (EmitInitException(propertyType))
-                            {
-                                return false;
-                            }
-
-                            EmitBindCoreCallForProperty(property, propertyType, expressionForPropertyAccess);
-                        }
-                        break;
+                    EmitBindCoreCallForMember(member, memberAccessExpr, sectionIdentifier, canSet);
                 }
 
-                return true;
+                _writer.WriteBlockEnd();
+                return success;
             }
 
-            private void EmitBindCoreCallForProperty(PropertySpec property, TypeSpec effectivePropertyType, string expressionForPropertyAccess)
+            private void EmitBindCoreCallForMember(
+                MemberSpec member,
+                string memberAccessExpr,
+                string configArgExpr,
+                bool canSet)
             {
-                _writer.WriteBlockStart($"if ({Identifier.HasChildren}({Identifier.section}))");
 
-                bool canGet = property.CanGet;
-                bool canSet = property.CanSet;
-                string effectivePropertyTypeDisplayString = effectivePropertyType.MinimalDisplayString;
+                TypeSpec memberType = member.Type;
+                TypeSpec effectiveMemberType = memberType.EffectiveType;
+                string effectiveMemberTypeDisplayString = effectiveMemberType.MinimalDisplayString;
+                bool canGet = member.CanGet;
 
-                string tempVarName = GetIncrementalVarName(Identifier.temp);
-                if (effectivePropertyType.IsValueType)
+                string tempIdentifier = GetIncrementalIdentifier(Identifier.temp);
+                InitializationKind initKind;
+                string targetObjAccessExpr;
+
+                if (effectiveMemberType.IsValueType)
                 {
-                    if (canSet)
+                    if (!canSet)
                     {
-                        if (canGet)
-                        {
-                            TypeSpec actualPropertyType = property.Type;
-                            if (actualPropertyType.SpecKind is TypeSpecKind.Nullable)
-                            {
-                                string nullableTempVarName = GetIncrementalVarName(Identifier.temp);
+                        return;
+                    }
 
-                                _writer.WriteLine($"{actualPropertyType.MinimalDisplayString} {nullableTempVarName} = {expressionForPropertyAccess};");
+                    Debug.Assert(canSet);
+                    initKind = InitializationKind.None;
 
-                                _writer.WriteLine(
-                                    $"{effectivePropertyTypeDisplayString} {tempVarName} = {nullableTempVarName}.{Identifier.HasValue} ? {nullableTempVarName}.{Identifier.Value} : new {effectivePropertyTypeDisplayString}();");
-                            }
-                            else
-                            {
-                                _writer.WriteLine($"{effectivePropertyTypeDisplayString} {tempVarName} = {expressionForPropertyAccess};");
-                            }
-                        }
-                        else
-                        {
-                            EmitObjectInit(effectivePropertyType, tempVarName, InitializationKind.Declaration);
-                        }
-
-                        _writer.WriteLine($@"{Identifier.BindCore}({Identifier.section}, ref {tempVarName}, {Identifier.binderOptions});");
-                        _writer.WriteLine($"{expressionForPropertyAccess} = {tempVarName};");
-                    }
-                }
-                else
-                {
-                    if (canGet)
+                    if (memberType.SpecKind is TypeSpecKind.Nullable)
                     {
-                        _writer.WriteLine($"{effectivePropertyTypeDisplayString} {tempVarName} = {expressionForPropertyAccess};");
-                        EmitObjectInit(effectivePropertyType, tempVarName, InitializationKind.AssignmentWithNullCheck);
-                        _writer.WriteLine($@"{Identifier.BindCore}({Identifier.section}, ref {tempVarName}, {Identifier.binderOptions});");
+                        string nullableTempIdentifier = GetIncrementalIdentifier(Identifier.temp);
 
-                        if (canSet)
-                        {
-                            _writer.WriteLine($"{expressionForPropertyAccess} = {tempVarName};");
-                        }
+                        _writer.WriteLine($"{memberType.MinimalDisplayString} {nullableTempIdentifier} = {memberAccessExpr};");
+
+                        _writer.WriteLine(
+                            $"{effectiveMemberTypeDisplayString} {tempIdentifier} = {nullableTempIdentifier}.{Identifier.HasValue} ? {nullableTempIdentifier}.{Identifier.Value} : new {effectiveMemberTypeDisplayString}();");
                     }
                     else
                     {
-                        Debug.Assert(canSet);
-                        EmitObjectInit(effectivePropertyType, tempVarName, InitializationKind.Declaration);
-                        _writer.WriteLine($@"{Identifier.BindCore}({Identifier.section}, ref {tempVarName}, {Identifier.binderOptions});");
-                        _writer.WriteLine($"{expressionForPropertyAccess} = {tempVarName};");
+                        _writer.WriteLine($"{effectiveMemberTypeDisplayString} {tempIdentifier} = {memberAccessExpr};");
                     }
-                }
 
-                _writer.WriteBlockEnd();
+                    targetObjAccessExpr = tempIdentifier;
+                }
+                else if (canGet)
+                {
+                    targetObjAccessExpr = memberAccessExpr;
+                    initKind = InitializationKind.AssignmentWithNullCheck;
+                }
+                else
+                {
+                    targetObjAccessExpr = memberAccessExpr;
+                    initKind = InitializationKind.SimpleAssignment;
+                }
+
+                Action<string>? writeOnSuccess = !canSet
+                     ? null
+                     : bindedValueIdentifier =>
+                         {
+                             if (memberAccessExpr != bindedValueIdentifier)
+                             {
+                                 _writer.WriteLine($"{memberAccessExpr} = {bindedValueIdentifier};");
+                             }
+                         };
+
+                EmitBindCoreCall(
+                    effectiveMemberType,
+                    targetObjAccessExpr,
+                    configArgExpr,
+                    initKind,
+                    writeOnSuccess);
             }
 
             private void EmitCollectionCastIfRequired(CollectionSpec type, out string objIdentifier)
@@ -903,22 +906,21 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
                 }
             }
 
-            private void EmitSwitchDefault(string caseLogic, bool addBreak = true)
-            {
-                _writer.WriteLine("default:");
-                _writer.Indentation++;
-                _writer.WriteBlockStart();
-                _writer.WriteBlock(caseLogic);
-                _writer.WriteBlockEnd();
+            private void Emit_Foreach_Section_In_ConfigChildren_BlockHeader() =>
+                _writer.WriteBlockStart($"foreach ({Identifier.IConfigurationSection} {Identifier.section} in {Identifier.configuration}.{Identifier.GetChildren}())");
 
-                if (addBreak)
-                {
-                    _writer.WriteLine("break;");
-                }
+            private static string GetSectionPathFromConfigurationExpression(string configurationKeyName)
+                => $@"{GetSectionFromConfigurationExpression(configurationKeyName)}.{Identifier.Path}";
 
-                _writer.Indentation--;
+            private static string GetSectionFromConfigurationExpression(string configurationKeyName, bool addQuotes = true)
+            {
+                string argExpr = addQuotes ? $@"""{configurationKeyName}""" : configurationKeyName;
+                return $@"{Expression.configurationGetSection}({argExpr})";
             }
 
+            private static string GetConfigKeyCacheFieldName(ObjectSpec type) =>
+                $"s_configKeys_{type.DisplayStringWithoutSpecialCharacters}";
+
             private void Emit_NotSupportedException_TypeNotDetectedAsInput() =>
                 _writer.WriteLine(@$"throw new global::System.NotSupportedException($""{string.Format(ExceptionMessages.TypeNotDetectedAsInput, "{type}")}"");");
         }
index dd4ba39..7325c29 100644 (file)
@@ -2,6 +2,7 @@
 // The .NET Foundation licenses this file to you under the MIT license.
 
 using System.Diagnostics;
+using System.Reflection;
 
 namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
 {
@@ -9,6 +10,8 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
     {
         private sealed partial class Emitter
         {
+            private static readonly AssemblyName s_assemblyName = typeof(Emitter).Assembly.GetName();
+
             private enum InitializationKind
             {
                 None = 0,
@@ -18,6 +21,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
             }
             private static class Expression
             {
+                public const string configurationGetSection = "configuration.GetSection";
                 public const string sectionKey = "section.Key";
                 public const string sectionPath = "section.Path";
                 public const string sectionValue = "section.Value";
@@ -30,7 +34,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
                 public const string ActionOfBinderOptions = $"global::System.Action<global::Microsoft.Extensions.Configuration.BinderOptions>";
                 public const string AddSingleton = $"{ServiceCollectionServiceExtensions}.AddSingleton";
                 public const string ConfigurationChangeTokenSource = "global::Microsoft.Extensions.Options.ConfigurationChangeTokenSource";
-                public const string CoreBindingHelper = $"global::{ConfigurationBindingGenerator.ProjectName}.{Identifier.CoreBindingHelper}";
+                public const string CoreBindingHelper = $"global::{ProjectName}.{Identifier.CoreBindingHelper}";
                 public const string IConfiguration = "global::Microsoft.Extensions.Configuration.IConfiguration";
                 public const string IConfigurationSection = IConfiguration + "Section";
                 public const string IOptionsChangeTokenSource = "global::Microsoft.Extensions.Options.IOptionsChangeTokenSource";
@@ -45,6 +49,9 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
             private static class MinimalDisplayString
             {
                 public const string NullableActionOfBinderOptions = "Action<BinderOptions>?";
+                public const string HashSetOfString = "HashSet<string>";
+                public const string LazyHashSetOfString = "Lazy<HashSet<string>>";
+                public const string ListOfString = "List<string>";
             }
 
             private static class Identifier
@@ -64,18 +71,19 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
                 public const string optionsBuilder = nameof(optionsBuilder);
                 public const string originalCount = nameof(originalCount);
                 public const string section = nameof(section);
+                public const string sectionKey = nameof(sectionKey);
                 public const string services = nameof(services);
-                public const string stringValue = nameof(stringValue);
                 public const string temp = nameof(temp);
                 public const string type = nameof(type);
+                public const string validateKeys = nameof(validateKeys);
+                public const string value = nameof(value);
 
                 public const string Add = nameof(Add);
                 public const string AddSingleton = nameof(AddSingleton);
                 public const string Any = nameof(Any);
                 public const string Array = nameof(Array);
+                public const string AsConfigWithChildren = nameof(AsConfigWithChildren);
                 public const string Bind = nameof(Bind);
-                public const string BindCore = nameof(BindCore);
-                public const string BindCoreUntyped = nameof(BindCoreUntyped);
                 public const string BinderOptions = nameof(BinderOptions);
                 public const string Configure = nameof(Configure);
                 public const string CopyTo = nameof(CopyTo);
@@ -91,12 +99,9 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
                 public const string GeneratedServiceCollectionBinder = nameof(GeneratedServiceCollectionBinder);
                 public const string Get = nameof(Get);
                 public const string GetBinderOptions = nameof(GetBinderOptions);
-                public const string GetCore = nameof(GetCore);
                 public const string GetChildren = nameof(GetChildren);
                 public const string GetSection = nameof(GetSection);
                 public const string GetValue = nameof(GetValue);
-                public const string GetValueCore = nameof(GetValueCore);
-                public const string HasChildren = nameof(HasChildren);
                 public const string HasConfig = nameof(HasConfig);
                 public const string HasValueOrChildren = nameof(HasValueOrChildren);
                 public const string HasValue = nameof(HasValue);
@@ -115,6 +120,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
                 public const string TryGetValue = nameof(TryGetValue);
                 public const string TryParse = nameof(TryParse);
                 public const string Uri = nameof(Uri);
+                public const string ValidateConfigurationKeys = nameof(ValidateConfigurationKeys);
                 public const string Value = nameof(Value);
             }
 
@@ -125,12 +131,12 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
 
             private void EmitBlankLineIfRequired()
             {
-                if (_precedingBlockExists)
+                if (_emitBlankLineBeforeNextStatement)
                 {
                     _writer.WriteBlankLine();
                 }
 
-                _precedingBlockExists = true;
+                _emitBlankLineBeforeNextStatement = true;
             }
 
             private void EmitCheckForNullArgument_WithBlankLine_IfRequired(bool isValueType)
@@ -170,9 +176,31 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
                 return false;
             }
 
+            private void EmitRootBindingClassBlockStart(string className)
+            {
+                EmitBlankLineIfRequired();
+                _writer.WriteBlock($$"""
+                    /// <summary>Generated helper providing an AOT and linking compatible implementation for configuration binding.</summary>
+                    {{GetGeneratedCodeAttributeSrc()}}
+                    internal static class {{className}}
+                    {
+                    """);
+
+                _emitBlankLineBeforeNextStatement = false;
+            }
+
+            private string GetGeneratedCodeAttributeSrc()
+            {
+                string attributeRefExpr = _useFullyQualifiedNames ? $"global::System.CodeDom.Compiler.GeneratedCodeAttribute" : "GeneratedCode";
+                return $@"[{attributeRefExpr}(""{s_assemblyName.Name}"", ""{s_assemblyName.Version}"")]";
+            }
+
             private string GetInitException(string message) => $@"throw new {GetInvalidOperationDisplayName()}(""{message}"")";
 
-            private string GetIncrementalVarName(string prefix) => $"{prefix}{_parseValueCount++}";
+            private string GetIncrementalIdentifier(string prefix) => $"{prefix}{_valueSuffixIndex++}";
+
+            private string GetInitalizeMethodDisplayString(ObjectSpec type) =>
+                GetHelperMethodDisplayString($"{nameof(MethodsToGen_CoreBindingHelper.Initialize)}{type.DisplayStringWithoutSpecialCharacters}");
 
             private string GetTypeDisplayString(TypeSpec type) => _useFullyQualifiedNames ? type.FullyQualifiedDisplayString : type.MinimalDisplayString;
 
index e39e65e..0055b2b 100644 (file)
@@ -1,8 +1,6 @@
 ï»¿// Licensed to the .NET Foundation under one or more agreements.
 // The .NET Foundation licenses this file to you under the MIT license.
 
-using Microsoft.CodeAnalysis;
-
 namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
 {
     public sealed partial class ConfigurationBindingGenerator
@@ -18,10 +16,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
                     return;
                 }
 
-                EmitBlankLineIfRequired();
-                _writer.WriteLine("/// <summary>Generated helper providing an AOT and linking compatible implementation for configuration binding.</summary>");
-                _writer.WriteBlockStart($"internal static class {Identifier.GeneratedOptionsBuilderBinder}");
-                _precedingBlockExists = false;
+                EmitRootBindingClassBlockStart(Identifier.GeneratedOptionsBuilderBinder);
 
                 EmitBindMethods_Extensions_OptionsBuilder();
                 EmitBindConfigurationMethod();
@@ -44,7 +39,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
                     EmitMethodBlockStart("Bind", paramList, documentation);
                     _writer.WriteLine($"return global::{Identifier.GeneratedOptionsBuilderBinder}.Bind({Identifier.optionsBuilder}, {Identifier.configuration}, {Identifier.configureOptions}: null);");
                     _writer.WriteBlockEnd();
-                    _writer.WriteBlankLine();
                 }
 
                 EmitMethodBlockStart(
@@ -81,7 +75,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
 
                 _writer.WriteBlock($$"""
                 {{FullyQualifiedDisplayString.IConfiguration}} {{Identifier.section}} = string.Equals(string.Empty, {{Identifier.configSectionPath}}, global::System.StringComparison.OrdinalIgnoreCase) ? {{Identifier.configuration}} : {{Identifier.configuration}}.{{Identifier.GetSection}}({{Identifier.configSectionPath}});
-                {{FullyQualifiedDisplayString.CoreBindingHelper}}.{{Identifier.BindCoreUntyped}}({{Identifier.section}}, {{Identifier.obj}}, typeof({{Identifier.TOptions}}), {{Identifier.configureOptions}});
+                {{FullyQualifiedDisplayString.CoreBindingHelper}}.{{nameof(MethodsToGen_CoreBindingHelper.BindCoreUntyped)}}({{Identifier.section}}, {{Identifier.obj}}, typeof({{Identifier.TOptions}}), {{Identifier.configureOptions}});
             """);
 
                 _writer.WriteBlockEnd(");");
index 8dd6a00..5b4edc9 100644 (file)
@@ -1,8 +1,6 @@
 ï»¿// Licensed to the .NET Foundation under one or more agreements.
 // The .NET Foundation licenses this file to you under the MIT license.
 
-using Microsoft.CodeAnalysis;
-
 namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
 {
     public sealed partial class ConfigurationBindingGenerator
@@ -18,11 +16,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
                     return;
                 }
 
-                EmitBlankLineIfRequired();
-
-                _writer.WriteLine("/// <summary>Generated helper providing an AOT and linking compatible implementation for configuration binding.</summary>");
-                _writer.WriteBlockStart($"internal static class {Identifier.GeneratedServiceCollectionBinder}");
-                _precedingBlockExists = false;
+                EmitRootBindingClassBlockStart(Identifier.GeneratedServiceCollectionBinder);
 
                 const string defaultNameExpr = "string.Empty";
                 const string configureMethodString = $"global::{Identifier.GeneratedServiceCollectionBinder}.{Identifier.Configure}";
@@ -52,7 +46,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
                 }
 
                 string optionsNamespaceName = "global::Microsoft.Extensions.Options";
-                string bindCoreUntypedDisplayString = GetHelperMethodDisplayString(Identifier.BindCoreUntyped);
+                string bindCoreUntypedDisplayString = GetHelperMethodDisplayString(nameof(MethodsToGen_CoreBindingHelper.BindCoreUntyped));
 
                 EmitBlockStart(paramList: $"string? {Identifier.name}, " + configParam + $", {FullyQualifiedDisplayString.ActionOfBinderOptions}? {Identifier.configureOptions}");
 
@@ -67,7 +61,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
                 """);
 
                 _writer.WriteBlockEnd();
-                _precedingBlockExists = true;
+                _emitBlankLineBeforeNextStatement = true;
             }
 
             private void EmitBlockStart(string paramList)
index 8f2c37a..f49deca 100644 (file)
@@ -14,6 +14,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
         GetCore = 0x4,
         GetValueCore = 0x8,
         Initialize = 0x10,
+        AsConfigWithChildren = 0x20,
     }
 
     /// <summary>
index 7db7c96..2628779 100644 (file)
@@ -111,7 +111,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
 
                     _sourceGenSpec.MethodsToGen_ConfigurationBinder |= overload;
                     typeSpecs.Add(typeSpec);
-                    RegisterTypeForMethodGen(MethodsToGen_CoreBindingHelper.BindCore, typeSpec);
                 }
 
                 static ITypeSymbol? ResolveType(IOperation conversionOperation) =>
@@ -124,6 +123,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
                         IMethodReferenceOperation m when m.Method.MethodKind == MethodKind.Constructor => m.Method.ContainingType,
                         IMethodReferenceOperation m => m.Method.ReturnType,
                         IAnonymousFunctionOperation f => f.Symbol.ReturnType,
+                        IParameterReferenceOperation p => p.Parameter.Type,
                         _ => null
                     };
             }
index 4787c69..f610209 100644 (file)
@@ -14,22 +14,6 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
         private static readonly char[] s_newLine = Environment.NewLine.ToCharArray();
         private int _indentation;
 
-
-        public int Indentation
-        {
-            get => _indentation;
-            set
-            {
-                if (value < 0)
-                {
-                    Throw();
-                    static void Throw() => throw new ArgumentOutOfRangeException(nameof(value));
-                }
-
-                _indentation = value;
-            }
-        }
-
         public void WriteBlockStart(string? declaration = null)
         {
             if (declaration is not null)
@@ -37,19 +21,19 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
                 WriteLine(declaration);
             }
             WriteLine("{");
-            Indentation++;
+            _indentation++;
         }
 
         public void WriteBlockEnd(string? extra = null)
         {
-            Indentation--;
-            Debug.Assert(Indentation > -1);
+            _indentation--;
+            Debug.Assert(_indentation > -1);
             WriteLine($"}}{extra}");
         }
 
         public void WriteLine(string source)
         {
-            _sb.Append(' ', 4 * Indentation);
+            _sb.Append(' ', 4 * _indentation);
             _sb.AppendLine(source);
         }
 
@@ -86,7 +70,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
 
         public SourceText ToSourceText()
         {
-            Debug.Assert(Indentation == 0 && _sb.Length > 0);
+            Debug.Assert(_indentation == 0 && _sb.Length > 0);
             return SourceText.From(_sb.ToString(), Encoding.UTF8);
         }
 
@@ -123,7 +107,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
 
         private unsafe void WriteLine(ReadOnlySpan<char> source)
         {
-            _sb.Append(' ', 4 * Indentation);
+            _sb.Append(' ', 4 * _indentation);
             fixed (char* ptr = source)
             {
                 _sb.Append(ptr, source.Length);
index fc2d2ba..f1c71a5 100644 (file)
@@ -41,6 +41,7 @@
     <Compile Include="Model\ConfigurationSectionSpec.cs" />
     <Compile Include="Model\InitializationStrategy.cs" />
     <Compile Include="Model\KnownTypeSymbols.cs" />
+    <Compile Include="Model\MemberSpec.cs" />
     <Compile Include="Model\NullableSpec.cs" />
     <Compile Include="Model\ObjectSpec.cs" />
     <Compile Include="Model\ParameterSpec.cs" />
index 7c90701..280ecc4 100644 (file)
@@ -17,11 +17,13 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
 
         public required CollectionPopulationStrategy PopulationStrategy { get; init; }
 
-        public override bool CanInitialize => ConcreteType?.CanInitialize ?? CanInitCompexType;
+        public override bool CanInitialize => ConcreteType?.CanInitialize ?? CanInitComplexObject();
 
         public override required InitializationStrategy InitializationStrategy { get; set; }
 
         public required string? ToEnumerableMethodCall { get; init; }
+
+        public sealed override bool NeedsMemberBinding => true;
     }
 
     internal sealed record EnumerableSpec : CollectionSpec
diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Model/MemberSpec.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Model/MemberSpec.cs
new file mode 100644 (file)
index 0000000..4bf674f
--- /dev/null
@@ -0,0 +1,28 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Diagnostics;
+using Microsoft.CodeAnalysis;
+
+namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
+{
+    internal abstract record MemberSpec
+    {
+        public MemberSpec(ISymbol member)
+        {
+            Debug.Assert(member is IPropertySymbol or IParameterSymbol);
+            Name = member.Name;
+            DefaultValueExpr = "default";
+        }
+
+        public string Name { get; }
+        public bool ErrorOnFailedBinding { get; protected set; }
+        public string DefaultValueExpr { get; protected set; }
+
+        public required TypeSpec Type { get; init; }
+        public required string ConfigurationKeyName { get; init; }
+
+        public abstract bool CanGet { get; }
+        public abstract bool CanSet { get; }
+    }
+}
index c59e17e..9dcca27 100644 (file)
@@ -1,23 +1,21 @@
 ï»¿// Licensed to the .NET Foundation under one or more agreements.
 // The .NET Foundation licenses this file to you under the MIT license.
 
-using System;
 using Microsoft.CodeAnalysis;
 
 namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
 {
     internal sealed record NullableSpec : TypeSpec
     {
-        public NullableSpec(ITypeSymbol type) : base(type) { }
+        private readonly TypeSpec _underlyingType;
 
-        public override TypeSpecKind SpecKind => TypeSpecKind.Nullable;
-
-        public required TypeSpec UnderlyingType { get; init; }
-
-        public override string? InitExceptionMessage
+        public NullableSpec(ITypeSymbol type, TypeSpec underlyingType) : base(type)
         {
-            get => UnderlyingType.InitExceptionMessage;
-            set => throw new InvalidOperationException();
+            _underlyingType = underlyingType;
         }
+
+        public override TypeSpecKind SpecKind => TypeSpecKind.Nullable;
+
+        public override TypeSpec EffectiveType => _underlyingType;
     }
 }
index 4dbfc4a..1696ee0 100644 (file)
@@ -3,27 +3,31 @@
 
 using System;
 using System.Collections.Generic;
+using System.Linq;
 using Microsoft.CodeAnalysis;
 
 namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
 {
     internal sealed record ObjectSpec : TypeSpec
     {
-        public ObjectSpec(INamedTypeSymbol type) : base(type)
-        {
-            InitializeMethodDisplayString = $"Initialize{type.Name.Replace(".", string.Empty).Replace("<", string.Empty).Replace(">", string.Empty)}";
-        }
+        public ObjectSpec(INamedTypeSymbol type) : base(type) { }
 
         public override TypeSpecKind SpecKind => TypeSpecKind.Object;
 
         public override InitializationStrategy InitializationStrategy { get; set; }
 
-        public override bool CanInitialize => CanInitCompexType;
+        public override bool CanInitialize => CanInitComplexObject();
 
         public Dictionary<string, PropertySpec> Properties { get; } = new(StringComparer.OrdinalIgnoreCase);
 
         public List<ParameterSpec> ConstructorParameters { get; } = new();
 
-        public string? InitializeMethodDisplayString { get; }
+        private string _displayStringWithoutSpecialCharacters;
+        public string DisplayStringWithoutSpecialCharacters =>
+            _displayStringWithoutSpecialCharacters ??= $"{MinimalDisplayString.Replace(".", string.Empty).Replace("<", string.Empty).Replace(">", string.Empty)}";
+
+        public override bool NeedsMemberBinding => CanInitialize &&
+            Properties.Values.Count > 0 &&
+            Properties.Values.Any(p => p.ShouldBind());
     }
 }
index a62f608..9b5e436 100644 (file)
@@ -6,31 +6,30 @@ using Microsoft.CodeAnalysis.CSharp;
 
 namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
 {
-    internal sealed record ParameterSpec
+    internal sealed record ParameterSpec : MemberSpec
     {
-        public ParameterSpec(IParameterSymbol parameter)
+        public ParameterSpec(IParameterSymbol parameter) : base(parameter)
         {
-            Name = parameter.Name;
             RefKind = parameter.RefKind;
 
-            HasExplicitDefaultValue = parameter.HasExplicitDefaultValue;
-            if (HasExplicitDefaultValue)
+            if (parameter.HasExplicitDefaultValue)
             {
                 string formatted = SymbolDisplay.FormatPrimitive(parameter.ExplicitDefaultValue, quoteStrings: true, useHexadecimalNumbers: false);
-                DefaultValue = formatted is "null" ? "default!" : formatted;
+                if (formatted is not "null")
+                {
+                    DefaultValueExpr = formatted;
+                }
+            }
+            else
+            {
+                ErrorOnFailedBinding = true;
             }
         }
 
-        public required TypeSpec Type { get; init; }
-
-        public string Name { get; }
-
-        public required string ConfigurationKeyName { get; init; }
-
         public RefKind RefKind { get; }
 
-        public bool HasExplicitDefaultValue { get; init; }
+        public override bool CanGet => false;
 
-        public string DefaultValue { get; } = "default!";
+        public override bool CanSet => true;
     }
 }
index 7f910a0..6b5bb5b 100644 (file)
@@ -19,7 +19,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
         {
             get
             {
-                Debug.Assert(StringParsableTypeKind is not StringParsableTypeKind.ConfigValue);
+                Debug.Assert(StringParsableTypeKind is not StringParsableTypeKind.AssignFromSectionValue);
 
                 _parseMethodName ??= StringParsableTypeKind is StringParsableTypeKind.ByteArray
                     ? "ParseByteArray"
@@ -34,7 +34,11 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
     internal enum StringParsableTypeKind
     {
         None = 0,
-        ConfigValue = 1,
+
+        /// <summary>
+        /// Declared types that can be assigned directly from IConfigurationSection.Value, i.e. string and tyepof(object).
+        /// </summary>
+        AssignFromSectionValue = 1,
         Enum = 2,
         ByteArray = 3,
         Integer = 4,
index 35d79a2..584e8d5 100644 (file)
@@ -5,43 +5,30 @@ using Microsoft.CodeAnalysis;
 
 namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
 {
-    internal sealed record PropertySpec
+    internal sealed record PropertySpec : MemberSpec
     {
-        public PropertySpec(IPropertySymbol property)
+        public PropertySpec(IPropertySymbol property) : base(property)
         {
-            Name = property.Name;
-            IsStatic = property.IsStatic;
+            IMethodSymbol? setMethod = property.SetMethod;
+            bool setterIsPublic = setMethod?.DeclaredAccessibility is Accessibility.Public;
+            bool isInitOnly = setMethod?.IsInitOnly is true;
 
-            bool setterIsPublic = property.SetMethod?.DeclaredAccessibility is Accessibility.Public;
-            IsInitOnly = property.SetMethod?.IsInitOnly == true;
-            IsRequired = property.IsRequired;
-            SetOnInit = setterIsPublic && (IsInitOnly || IsRequired);
-            CanSet = setterIsPublic && !IsInitOnly;
+            IsStatic = property.IsStatic;
+            SetOnInit = setterIsPublic && (property.IsRequired || isInitOnly);
+            CanSet = setterIsPublic && !isInitOnly;
             CanGet = property.GetMethod?.DeclaredAccessibility is Accessibility.Public;
         }
 
-        public required TypeSpec Type { get; init; }
-
         public ParameterSpec? MatchingCtorParam { get; set; }
 
-        public string Name { get; }
-
         public bool IsStatic { get; }
 
-        public bool IsRequired { get; }
-
-        public bool IsInitOnly { get; }
-
         public bool SetOnInit { get; }
 
-        public bool CanGet { get; }
-
-        public bool CanSet { get; }
+        public override bool CanGet { get; }
 
-        public required string ConfigurationKeyName { get; init; }
+        public override bool CanSet { get; }
 
-        public bool ShouldBind() =>
-            (CanGet || CanSet) &&
-            !(!CanSet && (Type as CollectionSpec)?.InitializationStrategy is InitializationStrategy.ParameterizedConstructor);
+        public bool ShouldBind() => CanGet || CanSet;
     }
 }
index 941320e..88c4b24 100644 (file)
@@ -11,13 +11,17 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
         public Dictionary<MethodsToGen_ConfigurationBinder, HashSet<TypeSpec>> TypesForGen_ConfigurationBinder_BindMethods { get; } = new();
 
         public HashSet<ParsableFromStringSpec> PrimitivesForHelperGen { get; } = new();
-        public HashSet<string> TypeNamespaces { get; } = new() { "Microsoft.Extensions.Configuration", "System.Globalization" };
+        public HashSet<string> TypeNamespaces { get; } = new()
+        {
+            "System",
+            "System.CodeDom.Compiler",
+            "System.Globalization",
+            "Microsoft.Extensions.Configuration",
+        };
 
         public MethodsToGen_CoreBindingHelper MethodsToGen_CoreBindingHelper { get; set; }
         public MethodsToGen_ConfigurationBinder MethodsToGen_ConfigurationBinder { get; set; }
         public MethodsToGen_Extensions_OptionsBuilder MethodsToGen_OptionsBuilderExt { get; set; }
         public MethodsToGen_Extensions_ServiceCollection MethodsToGen_ServiceCollectionExt { get; set; }
-
-        public bool ShouldEmitHasChildren { get; set; }
     }
 }
index f53b929..6a6292b 100644 (file)
@@ -20,6 +20,7 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
             FullyQualifiedDisplayString = type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
             MinimalDisplayString = type.ToDisplayString(s_minimalDisplayFormat);
             Name = Namespace + "." + MinimalDisplayString.Replace(".", "+");
+            IsInterface = type.TypeKind is TypeKind.Interface;
         }
 
         public string Name { get; }
@@ -40,12 +41,13 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
 
         public virtual bool CanInitialize => true;
 
-        /// <summary>
-        /// Location in the input compilation we picked up a call to Bind, Get, or Configure.
-        /// </summary>
-        public required Location? Location { get; init; }
+        public virtual bool NeedsMemberBinding { get; }
 
-        protected bool CanInitCompexType => InitializationStrategy is not InitializationStrategy.None && InitExceptionMessage is null;
+        public virtual TypeSpec EffectiveType => this;
+
+        public bool IsInterface { get; }
+
+        protected bool CanInitComplexObject() => InitializationStrategy is not InitializationStrategy.None && InitExceptionMessage is null;
     }
 
     internal enum TypeSpecKind
index fadaa1c..fb51320 100644 (file)
@@ -15,7 +15,7 @@ namespace Microsoft.Extensions
 #endif
     .Configuration.Binder.Tests
 {
-    public partial class ConfigurationBinderCollectionTests
+    public sealed partial class ConfigurationBinderCollectionTests : ConfigurationBinderTestsBase
     {
         [Fact]
         public void GetList()
index c2068fe..5e4855a 100644 (file)
@@ -36,6 +36,15 @@ namespace Microsoft.Extensions
             public T Value { get; set; }
         }
 
+        public record GenericOptionsRecord<T>(T Value);
+
+        public class GenericOptionsWithParamCtor<T>
+        {
+            public GenericOptionsWithParamCtor(T value) => Value = value;
+
+            public T Value { get; }
+        }
+
         public class OptionsWithNesting
         {
             public NestedOptions Nested { get; set; }
index 7537749..d9dfefe 100644 (file)
@@ -19,7 +19,17 @@ namespace Microsoft.Extensions
 #endif
     .Configuration.Binder.Tests
 {
-    public partial class ConfigurationBinderTests
+    public abstract class ConfigurationBinderTestsBase
+    {
+        public ConfigurationBinderTestsBase()
+        {
+#if LAUNCH_DEBUGGER
+if (!System.Diagnostics.Debugger.IsAttached) { System.Diagnostics.Debugger.Launch(); }
+#endif
+        }
+    }
+
+    public sealed partial class ConfigurationBinderTests : ConfigurationBinderTestsBase
     {
         [Fact]
         public void BindWithNestedTypesWithReadOnlyProperties()
@@ -1870,5 +1880,25 @@ namespace Microsoft.Extensions
             Assert.Equal(0, obj.Int32);
             Assert.False(obj.Boolean);
         }
+
+        [Fact]
+        public void AllowsCaseInsensitiveMatch()
+        {
+            var configuration = TestHelpers.GetConfigurationFromJsonString("""
+                {
+                    "vaLue": "MyString",
+                }
+                """);
+
+            GenericOptions<string> obj = new();
+            configuration.Bind(obj);
+            Assert.Equal("MyString", obj.Value);
+
+            GenericOptionsRecord<string> obj1 = configuration.Get<GenericOptionsRecord<string>>();
+            Assert.Equal("MyString", obj1.Value);
+
+            GenericOptionsWithParamCtor<string> obj2 = configuration.Get<GenericOptionsWithParamCtor<string>>();
+            Assert.Equal("MyString", obj2.Value);
+        }
     }
 }
index 2965e8d..f0b2ffb 100644 (file)
@@ -1,7 +1,9 @@
 // <auto-generated/>
 #nullable enable
+#pragma warning disable CS0612, CS0618 // Suppress warnings about [Obsolete] member usage in generated code.
 
 /// <summary>Generated helper providing an AOT and linking compatible implementation for configuration binding.</summary>
+[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")]
 internal static class GeneratedConfigurationBinder
 {
     /// <summary>Attempts to bind the configuration instance to a new instance of type T.</summary>
@@ -12,13 +14,17 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
 {
     using Microsoft.Extensions.Configuration;
     using System;
+    using System.CodeDom.Compiler;
     using System.Collections.Generic;
     using System.Globalization;
     using System.Linq;
 
     /// <summary>Provide core binding logic.</summary>
-    internal static class CoreBindingHelper
+    [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")]
+    file static class CoreBindingHelper
     {
+        private readonly static Lazy<HashSet<string>> s_configKeys_ProgramMyClassWithCustomCollections = new(() => new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "CustomDictionary", "CustomList", "IReadOnlyList", "IReadOnlyDictionary" });
+
         public static object? GetCore(this IConfiguration configuration, Type type, Action<BinderOptions>? configureOptions)
         {
             if (configuration is null)
@@ -52,9 +58,9 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
 
             foreach (IConfigurationSection section in configuration.GetChildren())
             {
-                if (section.Value is string stringValue2)
+                if (section.Value is string value)
                 {
-                    obj[section.Key!] = ParseInt(stringValue2, () => section.Path)!;
+                    obj[section.Key] = ParseInt(value, () => section.Path);
                 }
             }
         }
@@ -68,9 +74,41 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
 
             foreach (IConfigurationSection section in configuration.GetChildren())
             {
-                if (section.Value is string stringValue3)
+                if (section.Value is string value)
                 {
-                    obj.Add(stringValue3!);
+                    obj.Add(value);
+                }
+            }
+        }
+
+        public static void BindCore(IConfiguration configuration, ref List<int> obj, BinderOptions? binderOptions)
+        {
+            if (obj is null)
+            {
+                throw new ArgumentNullException(nameof(obj));
+            }
+
+            foreach (IConfigurationSection section in configuration.GetChildren())
+            {
+                if (section.Value is string value)
+                {
+                    obj.Add(ParseInt(value, () => section.Path));
+                }
+            }
+        }
+
+        public static void BindCore(IConfiguration configuration, ref ICollection<int> obj, BinderOptions? binderOptions)
+        {
+            if (obj is null)
+            {
+                throw new ArgumentNullException(nameof(obj));
+            }
+
+            foreach (IConfigurationSection section in configuration.GetChildren())
+            {
+                if (section.Value is string value)
+                {
+                    obj.Add(ParseInt(value, () => section.Path));
                 }
             }
         }
@@ -89,9 +127,41 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
 
             foreach (IConfigurationSection section in configuration.GetChildren())
             {
-                if (section.Value is string stringValue4)
+                if (section.Value is string value)
+                {
+                    temp.Add(ParseInt(value, () => section.Path));
+                }
+            }
+        }
+
+        public static void BindCore(IConfiguration configuration, ref Dictionary<string, int> obj, BinderOptions? binderOptions)
+        {
+            if (obj is null)
+            {
+                throw new ArgumentNullException(nameof(obj));
+            }
+
+            foreach (IConfigurationSection section in configuration.GetChildren())
+            {
+                if (section.Value is string value)
                 {
-                    temp.Add(ParseInt(stringValue4, () => section.Path)!);
+                    obj[section.Key] = ParseInt(value, () => section.Path);
+                }
+            }
+        }
+
+        public static void BindCore(IConfiguration configuration, ref IDictionary<string, int> obj, BinderOptions? binderOptions)
+        {
+            if (obj is null)
+            {
+                throw new ArgumentNullException(nameof(obj));
+            }
+
+            foreach (IConfigurationSection section in configuration.GetChildren())
+            {
+                if (section.Value is string value)
+                {
+                    obj[section.Key] = ParseInt(value, () => section.Path);
                 }
             }
         }
@@ -110,9 +180,9 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
 
             foreach (IConfigurationSection section in configuration.GetChildren())
             {
-                if (section.Value is string stringValue6)
+                if (section.Value is string value)
                 {
-                    temp[section.Key!] = ParseInt(stringValue6, () => section.Path)!;
+                    temp[section.Key] = ParseInt(value, () => section.Path);
                 }
             }
         }
@@ -124,69 +194,58 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
                 throw new ArgumentNullException(nameof(obj));
             }
 
-            List<string>? temp = null;
-            foreach (IConfigurationSection section in configuration.GetChildren())
+            ValidateConfigurationKeys(typeof(Program.MyClassWithCustomCollections), s_configKeys_ProgramMyClassWithCustomCollections, configuration, binderOptions);
+
+            if (AsConfigWithChildren(configuration.GetSection("CustomDictionary")) is IConfigurationSection section1)
             {
-                switch (section.Key)
-                {
-                    case "CustomDictionary":
-                        {
-                            if (HasChildren(section))
-                            {
-                                Program.CustomDictionary<string, int> temp7 = obj.CustomDictionary;
-                                temp7 ??= new Program.CustomDictionary<string, int>();
-                                BindCore(section, ref temp7, binderOptions);
-                                obj.CustomDictionary = temp7;
-                            }
-                        }
-                        break;
-                    case "CustomList":
-                        {
-                            if (HasChildren(section))
-                            {
-                                Program.CustomList temp8 = obj.CustomList;
-                                temp8 ??= new Program.CustomList();
-                                BindCore(section, ref temp8, binderOptions);
-                                obj.CustomList = temp8;
-                            }
-                        }
-                        break;
-                    case "IReadOnlyList":
-                        {
-                            if (HasChildren(section))
-                            {
-                                IReadOnlyList<int> temp9 = obj.IReadOnlyList;
-                                temp9 = temp9 is null ? new List<int>() : new List<int>(temp9);
-                                BindCore(section, ref temp9, binderOptions);
-                                obj.IReadOnlyList = temp9;
-                            }
-                        }
-                        break;
-                    case "IReadOnlyDictionary":
-                        {
-                            if (HasChildren(section))
-                            {
-                                IReadOnlyDictionary<string, int> temp10 = obj.IReadOnlyDictionary;
-                                temp10 = temp10 is null ? new Dictionary<string, int>() : temp10.ToDictionary(pair => pair.Key, pair => pair.Value);
-                                BindCore(section, ref temp10, binderOptions);
-                                obj.IReadOnlyDictionary = temp10;
-                            }
-                        }
-                        break;
-                    default:
-                        {
-                            if (binderOptions?.ErrorOnUnknownConfiguration == true)
-                            {
-                                (temp ??= new List<string>()).Add($"'{section.Key}'");
-                            }
-                        }
-                        break;
-                }
+                Program.CustomDictionary<string, int> temp3 = obj.CustomDictionary;
+                temp3 ??= new Program.CustomDictionary<string, int>();
+                BindCore(section1, ref temp3, binderOptions);
+                obj.CustomDictionary = temp3;
+            }
+
+            if (AsConfigWithChildren(configuration.GetSection("CustomList")) is IConfigurationSection section4)
+            {
+                Program.CustomList temp6 = obj.CustomList;
+                temp6 ??= new Program.CustomList();
+                BindCore(section4, ref temp6, binderOptions);
+                obj.CustomList = temp6;
+            }
+
+            if (AsConfigWithChildren(configuration.GetSection("IReadOnlyList")) is IConfigurationSection section7)
+            {
+                IReadOnlyList<int> temp9 = obj.IReadOnlyList;
+                temp9 = temp9 is null ? new List<int>() : new List<int>(temp9);
+                BindCore(section7, ref temp9, binderOptions);
+                obj.IReadOnlyList = temp9;
+            }
+
+            if (AsConfigWithChildren(configuration.GetSection("IReadOnlyDictionary")) is IConfigurationSection section10)
+            {
+                IReadOnlyDictionary<string, int> temp12 = obj.IReadOnlyDictionary;
+                temp12 = temp12 is null ? new Dictionary<string, int>() : temp12.ToDictionary(pair => pair.Key, pair => pair.Value);
+                BindCore(section10, ref temp12, binderOptions);
+                obj.IReadOnlyDictionary = temp12;
             }
+        }
 
-            if (temp is not null)
+        /// <summary>If required by the binder options, validates that there are no unknown keys in the input configuration object.</summary>
+        public static void ValidateConfigurationKeys(Type type, Lazy<HashSet<string>> keys, IConfiguration configuration, BinderOptions? binderOptions)
+        {
+            if (binderOptions?.ErrorOnUnknownConfiguration is true)
             {
-                throw new InvalidOperationException($"'ErrorOnUnknownConfiguration' was set on the provided BinderOptions, but the following properties were not found on the instance of {typeof(Program.MyClassWithCustomCollections)}: {string.Join(", ", temp)}");
+                List<string>? temp = null;
+                foreach (IConfigurationSection section in configuration.GetChildren())
+                {
+                    if (!keys.Value.Contains(section.Key))
+                    {
+                        (temp ??= new List<string>()).Add($"'{section.Key}'");
+                    }
+                }
+                if (temp is not null)
+                {
+                    throw new InvalidOperationException($"'ErrorOnUnknownConfiguration' was set on the provided BinderOptions, but the following properties were not found on the instance of {type}: {string.Join(", ", temp)}");
+                }
             }
         }
 
@@ -196,16 +255,16 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
             {
                 return true;
             }
-            return HasChildren(configuration);
+            return AsConfigWithChildren(configuration) is not null;
         }
 
-        public static bool HasChildren(IConfiguration configuration)
+        public static IConfiguration? AsConfigWithChildren(IConfiguration configuration)
         {
-            foreach (IConfigurationSection section in configuration.GetChildren())
+            foreach (IConfigurationSection _ in configuration.GetChildren())
             {
-                return true;
+                return configuration;
             }
-            return false;
+            return null;
         }
 
         public static BinderOptions? GetBinderOptions(Action<BinderOptions>? configureOptions)
@@ -223,11 +282,11 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
             return binderOptions;
         }
 
-        public static int ParseInt(string stringValue, Func<string?> getPath)
+        public static int ParseInt(string value, Func<string?> getPath)
         {
             try
             {
-                return int.Parse(stringValue, NumberStyles.Integer, CultureInfo.InvariantCulture);
+                return int.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture);
             }
             catch (Exception exception)
             {
index 92b7270..260a7ad 100644 (file)
@@ -1,7 +1,9 @@
 // <auto-generated/>
 #nullable enable
+#pragma warning disable CS0612, CS0618 // Suppress warnings about [Obsolete] member usage in generated code.
 
 /// <summary>Generated helper providing an AOT and linking compatible implementation for configuration binding.</summary>
+[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")]
 internal static class GeneratedConfigurationBinder
 {
     /// <summary>Attempts to bind the given object instance to configuration values by matching property names against configuration keys recursively.</summary>
@@ -18,12 +20,16 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
 {
     using Microsoft.Extensions.Configuration;
     using System;
+    using System.CodeDom.Compiler;
     using System.Collections.Generic;
     using System.Globalization;
 
     /// <summary>Provide core binding logic.</summary>
-    internal static class CoreBindingHelper
+    [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")]
+    file static class CoreBindingHelper
     {
+        private readonly static Lazy<HashSet<string>> s_configKeys_ProgramMyClass = new(() => new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "MyString", "MyInt", "MyList", "MyDictionary", "MyComplexDictionary" });
+
         public static void BindCore(IConfiguration configuration, ref List<int> obj, BinderOptions? binderOptions)
         {
             if (obj is null)
@@ -33,9 +39,9 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
 
             foreach (IConfigurationSection section in configuration.GetChildren())
             {
-                if (section.Value is string stringValue0)
+                if (section.Value is string value)
                 {
-                    obj.Add(ParseInt(stringValue0, () => section.Path)!);
+                    obj.Add(ParseInt(value, () => section.Path));
                 }
             }
         }
@@ -49,22 +55,13 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
 
             foreach (IConfigurationSection section in configuration.GetChildren())
             {
-                if (section.Value is string stringValue2)
+                if (section.Value is string value)
                 {
-                    obj[section.Key!] = stringValue2!;
+                    obj[section.Key] = value;
                 }
             }
         }
 
-        public static void BindCore(IConfiguration configuration, ref Program.MyClass2 obj, BinderOptions? binderOptions)
-        {
-            if (obj is null)
-            {
-                throw new ArgumentNullException(nameof(obj));
-            }
-
-        }
-
         public static void BindCore(IConfiguration configuration, ref Dictionary<string, Program.MyClass2> obj, BinderOptions? binderOptions)
         {
             if (obj is null)
@@ -74,12 +71,11 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
 
             foreach (IConfigurationSection section in configuration.GetChildren())
             {
-                if (!(obj.TryGetValue(section.Key!, out Program.MyClass2? element) && element is not null))
+                if (!(obj.TryGetValue(section.Key, out Program.MyClass2? element) && element is not null))
                 {
                     element = new Program.MyClass2();
                 }
-                BindCore(section, ref element!, binderOptions);
-                obj[section.Key!] = element;
+                obj[section.Key] = element;
             }
         }
 
@@ -90,81 +86,67 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
                 throw new ArgumentNullException(nameof(obj));
             }
 
-            List<string>? temp = null;
-            foreach (IConfigurationSection section in configuration.GetChildren())
+            ValidateConfigurationKeys(typeof(Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions);
+
+            obj.MyString = configuration["MyString"]!;
+
+            if (configuration["MyInt"] is string value1)
             {
-                switch (section.Key)
-                {
-                    case "MyString":
-                        {
-                            obj.MyString = configuration["MyString"]!;
-                        }
-                        break;
-                    case "MyInt":
-                        {
-                            if (configuration["MyInt"] is string stringValue6)
-                            {
-                                obj.MyInt = ParseInt(stringValue6, () => section.Path)!;
-                            }
-                        }
-                        break;
-                    case "MyList":
-                        {
-                            if (HasChildren(section))
-                            {
-                                List<int> temp7 = obj.MyList;
-                                temp7 ??= new List<int>();
-                                BindCore(section, ref temp7, binderOptions);
-                                obj.MyList = temp7;
-                            }
-                        }
-                        break;
-                    case "MyDictionary":
-                        {
-                            if (HasChildren(section))
-                            {
-                                Dictionary<string, string> temp8 = obj.MyDictionary;
-                                temp8 ??= new Dictionary<string, string>();
-                                BindCore(section, ref temp8, binderOptions);
-                                obj.MyDictionary = temp8;
-                            }
-                        }
-                        break;
-                    case "MyComplexDictionary":
-                        {
-                            if (HasChildren(section))
-                            {
-                                Dictionary<string, Program.MyClass2> temp9 = obj.MyComplexDictionary;
-                                temp9 ??= new Dictionary<string, Program.MyClass2>();
-                                BindCore(section, ref temp9, binderOptions);
-                                obj.MyComplexDictionary = temp9;
-                            }
-                        }
-                        break;
-                    default:
-                        {
-                            if (binderOptions?.ErrorOnUnknownConfiguration == true)
-                            {
-                                (temp ??= new List<string>()).Add($"'{section.Key}'");
-                            }
-                        }
-                        break;
-                }
+                obj.MyInt = ParseInt(value1, () => configuration.GetSection("MyInt").Path);
+            }
+
+            if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section2)
+            {
+                List<int> temp4 = obj.MyList;
+                temp4 ??= new List<int>();
+                BindCore(section2, ref temp4, binderOptions);
+                obj.MyList = temp4;
+            }
+
+            if (AsConfigWithChildren(configuration.GetSection("MyDictionary")) is IConfigurationSection section5)
+            {
+                Dictionary<string, string> temp7 = obj.MyDictionary;
+                temp7 ??= new Dictionary<string, string>();
+                BindCore(section5, ref temp7, binderOptions);
+                obj.MyDictionary = temp7;
             }
 
-            if (temp is not null)
+            if (AsConfigWithChildren(configuration.GetSection("MyComplexDictionary")) is IConfigurationSection section8)
             {
-                throw new InvalidOperationException($"'ErrorOnUnknownConfiguration' was set on the provided BinderOptions, but the following properties were not found on the instance of {typeof(Program.MyClass)}: {string.Join(", ", temp)}");
+                Dictionary<string, Program.MyClass2> temp10 = obj.MyComplexDictionary;
+                temp10 ??= new Dictionary<string, Program.MyClass2>();
+                BindCore(section8, ref temp10, binderOptions);
+                obj.MyComplexDictionary = temp10;
             }
         }
 
-        public static bool HasChildren(IConfiguration configuration)
+        /// <summary>If required by the binder options, validates that there are no unknown keys in the input configuration object.</summary>
+        public static void ValidateConfigurationKeys(Type type, Lazy<HashSet<string>> keys, IConfiguration configuration, BinderOptions? binderOptions)
         {
-            foreach (IConfigurationSection section in configuration.GetChildren())
+            if (binderOptions?.ErrorOnUnknownConfiguration is true)
+            {
+                List<string>? temp = null;
+                foreach (IConfigurationSection section in configuration.GetChildren())
+                {
+                    if (!keys.Value.Contains(section.Key))
+                    {
+                        (temp ??= new List<string>()).Add($"'{section.Key}'");
+                    }
+                }
+                if (temp is not null)
+                {
+                    throw new InvalidOperationException($"'ErrorOnUnknownConfiguration' was set on the provided BinderOptions, but the following properties were not found on the instance of {type}: {string.Join(", ", temp)}");
+                }
+            }
+        }
+
+        public static IConfiguration? AsConfigWithChildren(IConfiguration configuration)
+        {
+            foreach (IConfigurationSection _ in configuration.GetChildren())
             {
-                return true;
+                return configuration;
             }
-            return false;
+            return null;
         }
 
         public static BinderOptions? GetBinderOptions(Action<BinderOptions>? configureOptions)
@@ -182,11 +164,11 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
             return binderOptions;
         }
 
-        public static int ParseInt(string stringValue, Func<string?> getPath)
+        public static int ParseInt(string value, Func<string?> getPath)
         {
             try
             {
-                return int.Parse(stringValue, NumberStyles.Integer, CultureInfo.InvariantCulture);
+                return int.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture);
             }
             catch (Exception exception)
             {
index f017f84..ecc9953 100644 (file)
@@ -1,7 +1,9 @@
-// <auto-generated/>
+// <auto-generated/>
 #nullable enable
+#pragma warning disable CS0612, CS0618 // Suppress warnings about [Obsolete] member usage in generated code.
 
 /// <summary>Generated helper providing an AOT and linking compatible implementation for configuration binding.</summary>
+[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")]
 internal static class GeneratedConfigurationBinder
 {
     /// <summary>Attempts to bind the given object instance to configuration values by matching property names against configuration keys recursively.</summary>
@@ -12,12 +14,16 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
 {
     using Microsoft.Extensions.Configuration;
     using System;
+    using System.CodeDom.Compiler;
     using System.Collections.Generic;
     using System.Globalization;
 
     /// <summary>Provide core binding logic.</summary>
-    internal static class CoreBindingHelper
+    [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")]
+    file static class CoreBindingHelper
     {
+        private readonly static Lazy<HashSet<string>> s_configKeys_ProgramMyClass = new(() => new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "MyString", "MyInt", "MyList", "MyDictionary", "MyComplexDictionary" });
+
         public static void BindCore(IConfiguration configuration, ref List<int> obj, BinderOptions? binderOptions)
         {
             if (obj is null)
@@ -27,9 +33,9 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
 
             foreach (IConfigurationSection section in configuration.GetChildren())
             {
-                if (section.Value is string stringValue0)
+                if (section.Value is string value)
                 {
-                    obj.Add(ParseInt(stringValue0, () => section.Path)!);
+                    obj.Add(ParseInt(value, () => section.Path));
                 }
             }
         }
@@ -43,22 +49,13 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
 
             foreach (IConfigurationSection section in configuration.GetChildren())
             {
-                if (section.Value is string stringValue2)
+                if (section.Value is string value)
                 {
-                    obj[section.Key!] = stringValue2!;
+                    obj[section.Key] = value;
                 }
             }
         }
 
-        public static void BindCore(IConfiguration configuration, ref Program.MyClass2 obj, BinderOptions? binderOptions)
-        {
-            if (obj is null)
-            {
-                throw new ArgumentNullException(nameof(obj));
-            }
-
-        }
-
         public static void BindCore(IConfiguration configuration, ref Dictionary<string, Program.MyClass2> obj, BinderOptions? binderOptions)
         {
             if (obj is null)
@@ -68,12 +65,11 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
 
             foreach (IConfigurationSection section in configuration.GetChildren())
             {
-                if (!(obj.TryGetValue(section.Key!, out Program.MyClass2? element) && element is not null))
+                if (!(obj.TryGetValue(section.Key, out Program.MyClass2? element) && element is not null))
                 {
                     element = new Program.MyClass2();
                 }
-                BindCore(section, ref element!, binderOptions);
-                obj[section.Key!] = element;
+                obj[section.Key] = element;
             }
         }
 
@@ -84,88 +80,74 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
                 throw new ArgumentNullException(nameof(obj));
             }
 
-            List<string>? temp = null;
-            foreach (IConfigurationSection section in configuration.GetChildren())
+            ValidateConfigurationKeys(typeof(Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions);
+
+            obj.MyString = configuration["MyString"]!;
+
+            if (configuration["MyInt"] is string value1)
             {
-                switch (section.Key)
-                {
-                    case "MyString":
-                        {
-                            obj.MyString = configuration["MyString"]!;
-                        }
-                        break;
-                    case "MyInt":
-                        {
-                            if (configuration["MyInt"] is string stringValue6)
-                            {
-                                obj.MyInt = ParseInt(stringValue6, () => section.Path)!;
-                            }
-                        }
-                        break;
-                    case "MyList":
-                        {
-                            if (HasChildren(section))
-                            {
-                                List<int> temp7 = obj.MyList;
-                                temp7 ??= new List<int>();
-                                BindCore(section, ref temp7, binderOptions);
-                                obj.MyList = temp7;
-                            }
-                        }
-                        break;
-                    case "MyDictionary":
-                        {
-                            if (HasChildren(section))
-                            {
-                                Dictionary<string, string> temp8 = obj.MyDictionary;
-                                temp8 ??= new Dictionary<string, string>();
-                                BindCore(section, ref temp8, binderOptions);
-                                obj.MyDictionary = temp8;
-                            }
-                        }
-                        break;
-                    case "MyComplexDictionary":
-                        {
-                            if (HasChildren(section))
-                            {
-                                Dictionary<string, Program.MyClass2> temp9 = obj.MyComplexDictionary;
-                                temp9 ??= new Dictionary<string, Program.MyClass2>();
-                                BindCore(section, ref temp9, binderOptions);
-                                obj.MyComplexDictionary = temp9;
-                            }
-                        }
-                        break;
-                    default:
-                        {
-                            if (binderOptions?.ErrorOnUnknownConfiguration == true)
-                            {
-                                (temp ??= new List<string>()).Add($"'{section.Key}'");
-                            }
-                        }
-                        break;
-                }
+                obj.MyInt = ParseInt(value1, () => configuration.GetSection("MyInt").Path);
+            }
+
+            if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section2)
+            {
+                List<int> temp4 = obj.MyList;
+                temp4 ??= new List<int>();
+                BindCore(section2, ref temp4, binderOptions);
+                obj.MyList = temp4;
+            }
+
+            if (AsConfigWithChildren(configuration.GetSection("MyDictionary")) is IConfigurationSection section5)
+            {
+                Dictionary<string, string> temp7 = obj.MyDictionary;
+                temp7 ??= new Dictionary<string, string>();
+                BindCore(section5, ref temp7, binderOptions);
+                obj.MyDictionary = temp7;
             }
 
-            if (temp is not null)
+            if (AsConfigWithChildren(configuration.GetSection("MyComplexDictionary")) is IConfigurationSection section8)
             {
-                throw new InvalidOperationException($"'ErrorOnUnknownConfiguration' was set on the provided BinderOptions, but the following properties were not found on the instance of {typeof(Program.MyClass)}: {string.Join(", ", temp)}");
+                Dictionary<string, Program.MyClass2> temp10 = obj.MyComplexDictionary;
+                temp10 ??= new Dictionary<string, Program.MyClass2>();
+                BindCore(section8, ref temp10, binderOptions);
+                obj.MyComplexDictionary = temp10;
             }
         }
 
-        public static bool HasChildren(IConfiguration configuration)
+        /// <summary>If required by the binder options, validates that there are no unknown keys in the input configuration object.</summary>
+        public static void ValidateConfigurationKeys(Type type, Lazy<HashSet<string>> keys, IConfiguration configuration, BinderOptions? binderOptions)
         {
-            foreach (IConfigurationSection section in configuration.GetChildren())
+            if (binderOptions?.ErrorOnUnknownConfiguration is true)
+            {
+                List<string>? temp = null;
+                foreach (IConfigurationSection section in configuration.GetChildren())
+                {
+                    if (!keys.Value.Contains(section.Key))
+                    {
+                        (temp ??= new List<string>()).Add($"'{section.Key}'");
+                    }
+                }
+                if (temp is not null)
+                {
+                    throw new InvalidOperationException($"'ErrorOnUnknownConfiguration' was set on the provided BinderOptions, but the following properties were not found on the instance of {type}: {string.Join(", ", temp)}");
+                }
+            }
+        }
+
+        public static IConfiguration? AsConfigWithChildren(IConfiguration configuration)
+        {
+            foreach (IConfigurationSection _ in configuration.GetChildren())
             {
-                return true;
+                return configuration;
             }
-            return false;
+            return null;
         }
 
-        public static int ParseInt(string stringValue, Func<string?> getPath)
+        public static int ParseInt(string value, Func<string?> getPath)
         {
             try
             {
-                return int.Parse(stringValue, NumberStyles.Integer, CultureInfo.InvariantCulture);
+                return int.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture);
             }
             catch (Exception exception)
             {
index f11815e..6132d8d 100644 (file)
@@ -1,7 +1,9 @@
-// <auto-generated/>
+// <auto-generated/>
 #nullable enable
+#pragma warning disable CS0612, CS0618 // Suppress warnings about [Obsolete] member usage in generated code.
 
 /// <summary>Generated helper providing an AOT and linking compatible implementation for configuration binding.</summary>
+[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")]
 internal static class GeneratedConfigurationBinder
 {
     /// <summary>Attempts to bind the given object instance to configuration values by matching property names against configuration keys recursively.</summary>
@@ -12,12 +14,16 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
 {
     using Microsoft.Extensions.Configuration;
     using System;
+    using System.CodeDom.Compiler;
     using System.Collections.Generic;
     using System.Globalization;
 
     /// <summary>Provide core binding logic.</summary>
-    internal static class CoreBindingHelper
+    [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")]
+    file static class CoreBindingHelper
     {
+        private readonly static Lazy<HashSet<string>> s_configKeys_ProgramMyClass = new(() => new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "MyString", "MyInt", "MyList", "MyDictionary", "MyComplexDictionary" });
+
         public static void BindCore(IConfiguration configuration, ref List<int> obj, BinderOptions? binderOptions)
         {
             if (obj is null)
@@ -27,9 +33,9 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
 
             foreach (IConfigurationSection section in configuration.GetChildren())
             {
-                if (section.Value is string stringValue0)
+                if (section.Value is string value)
                 {
-                    obj.Add(ParseInt(stringValue0, () => section.Path)!);
+                    obj.Add(ParseInt(value, () => section.Path));
                 }
             }
         }
@@ -43,22 +49,13 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
 
             foreach (IConfigurationSection section in configuration.GetChildren())
             {
-                if (section.Value is string stringValue2)
+                if (section.Value is string value)
                 {
-                    obj[section.Key!] = stringValue2!;
+                    obj[section.Key] = value;
                 }
             }
         }
 
-        public static void BindCore(IConfiguration configuration, ref Program.MyClass2 obj, BinderOptions? binderOptions)
-        {
-            if (obj is null)
-            {
-                throw new ArgumentNullException(nameof(obj));
-            }
-
-        }
-
         public static void BindCore(IConfiguration configuration, ref Dictionary<string, Program.MyClass2> obj, BinderOptions? binderOptions)
         {
             if (obj is null)
@@ -68,12 +65,11 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
 
             foreach (IConfigurationSection section in configuration.GetChildren())
             {
-                if (!(obj.TryGetValue(section.Key!, out Program.MyClass2? element) && element is not null))
+                if (!(obj.TryGetValue(section.Key, out Program.MyClass2? element) && element is not null))
                 {
                     element = new Program.MyClass2();
                 }
-                BindCore(section, ref element!, binderOptions);
-                obj[section.Key!] = element;
+                obj[section.Key] = element;
             }
         }
 
@@ -84,81 +80,67 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
                 throw new ArgumentNullException(nameof(obj));
             }
 
-            List<string>? temp = null;
-            foreach (IConfigurationSection section in configuration.GetChildren())
+            ValidateConfigurationKeys(typeof(Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions);
+
+            obj.MyString = configuration["MyString"]!;
+
+            if (configuration["MyInt"] is string value1)
             {
-                switch (section.Key)
-                {
-                    case "MyString":
-                        {
-                            obj.MyString = configuration["MyString"]!;
-                        }
-                        break;
-                    case "MyInt":
-                        {
-                            if (configuration["MyInt"] is string stringValue6)
-                            {
-                                obj.MyInt = ParseInt(stringValue6, () => section.Path)!;
-                            }
-                        }
-                        break;
-                    case "MyList":
-                        {
-                            if (HasChildren(section))
-                            {
-                                List<int> temp7 = obj.MyList;
-                                temp7 ??= new List<int>();
-                                BindCore(section, ref temp7, binderOptions);
-                                obj.MyList = temp7;
-                            }
-                        }
-                        break;
-                    case "MyDictionary":
-                        {
-                            if (HasChildren(section))
-                            {
-                                Dictionary<string, string> temp8 = obj.MyDictionary;
-                                temp8 ??= new Dictionary<string, string>();
-                                BindCore(section, ref temp8, binderOptions);
-                                obj.MyDictionary = temp8;
-                            }
-                        }
-                        break;
-                    case "MyComplexDictionary":
-                        {
-                            if (HasChildren(section))
-                            {
-                                Dictionary<string, Program.MyClass2> temp9 = obj.MyComplexDictionary;
-                                temp9 ??= new Dictionary<string, Program.MyClass2>();
-                                BindCore(section, ref temp9, binderOptions);
-                                obj.MyComplexDictionary = temp9;
-                            }
-                        }
-                        break;
-                    default:
-                        {
-                            if (binderOptions?.ErrorOnUnknownConfiguration == true)
-                            {
-                                (temp ??= new List<string>()).Add($"'{section.Key}'");
-                            }
-                        }
-                        break;
-                }
+                obj.MyInt = ParseInt(value1, () => configuration.GetSection("MyInt").Path);
+            }
+
+            if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section2)
+            {
+                List<int> temp4 = obj.MyList;
+                temp4 ??= new List<int>();
+                BindCore(section2, ref temp4, binderOptions);
+                obj.MyList = temp4;
+            }
+
+            if (AsConfigWithChildren(configuration.GetSection("MyDictionary")) is IConfigurationSection section5)
+            {
+                Dictionary<string, string> temp7 = obj.MyDictionary;
+                temp7 ??= new Dictionary<string, string>();
+                BindCore(section5, ref temp7, binderOptions);
+                obj.MyDictionary = temp7;
             }
 
-            if (temp is not null)
+            if (AsConfigWithChildren(configuration.GetSection("MyComplexDictionary")) is IConfigurationSection section8)
             {
-                throw new InvalidOperationException($"'ErrorOnUnknownConfiguration' was set on the provided BinderOptions, but the following properties were not found on the instance of {typeof(Program.MyClass)}: {string.Join(", ", temp)}");
+                Dictionary<string, Program.MyClass2> temp10 = obj.MyComplexDictionary;
+                temp10 ??= new Dictionary<string, Program.MyClass2>();
+                BindCore(section8, ref temp10, binderOptions);
+                obj.MyComplexDictionary = temp10;
             }
         }
 
-        public static bool HasChildren(IConfiguration configuration)
+        /// <summary>If required by the binder options, validates that there are no unknown keys in the input configuration object.</summary>
+        public static void ValidateConfigurationKeys(Type type, Lazy<HashSet<string>> keys, IConfiguration configuration, BinderOptions? binderOptions)
         {
-            foreach (IConfigurationSection section in configuration.GetChildren())
+            if (binderOptions?.ErrorOnUnknownConfiguration is true)
+            {
+                List<string>? temp = null;
+                foreach (IConfigurationSection section in configuration.GetChildren())
+                {
+                    if (!keys.Value.Contains(section.Key))
+                    {
+                        (temp ??= new List<string>()).Add($"'{section.Key}'");
+                    }
+                }
+                if (temp is not null)
+                {
+                    throw new InvalidOperationException($"'ErrorOnUnknownConfiguration' was set on the provided BinderOptions, but the following properties were not found on the instance of {type}: {string.Join(", ", temp)}");
+                }
+            }
+        }
+
+        public static IConfiguration? AsConfigWithChildren(IConfiguration configuration)
+        {
+            foreach (IConfigurationSection _ in configuration.GetChildren())
             {
-                return true;
+                return configuration;
             }
-            return false;
+            return null;
         }
 
         public static BinderOptions? GetBinderOptions(Action<BinderOptions>? configureOptions)
@@ -176,11 +158,11 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
             return binderOptions;
         }
 
-        public static int ParseInt(string stringValue, Func<string?> getPath)
+        public static int ParseInt(string value, Func<string?> getPath)
         {
             try
             {
-                return int.Parse(stringValue, NumberStyles.Integer, CultureInfo.InvariantCulture);
+                return int.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture);
             }
             catch (Exception exception)
             {
index d96b24a..0057027 100644 (file)
@@ -1,7 +1,9 @@
-// <auto-generated/>
+// <auto-generated/>
 #nullable enable
+#pragma warning disable CS0612, CS0618 // Suppress warnings about [Obsolete] member usage in generated code.
 
 /// <summary>Generated helper providing an AOT and linking compatible implementation for configuration binding.</summary>
+[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")]
 internal static class GeneratedConfigurationBinder
 {
     /// <summary>Attempts to bind the given object instance to configuration values by matching property names against configuration keys recursively.</summary>
@@ -12,12 +14,16 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
 {
     using Microsoft.Extensions.Configuration;
     using System;
+    using System.CodeDom.Compiler;
     using System.Collections.Generic;
     using System.Globalization;
 
     /// <summary>Provide core binding logic.</summary>
-    internal static class CoreBindingHelper
+    [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")]
+    file static class CoreBindingHelper
     {
+        private readonly static Lazy<HashSet<string>> s_configKeys_ProgramMyClass = new(() => new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "MyString", "MyInt", "MyList", "MyDictionary", "MyComplexDictionary" });
+
         public static void BindCore(IConfiguration configuration, ref List<int> obj, BinderOptions? binderOptions)
         {
             if (obj is null)
@@ -27,9 +33,9 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
 
             foreach (IConfigurationSection section in configuration.GetChildren())
             {
-                if (section.Value is string stringValue0)
+                if (section.Value is string value)
                 {
-                    obj.Add(ParseInt(stringValue0, () => section.Path)!);
+                    obj.Add(ParseInt(value, () => section.Path));
                 }
             }
         }
@@ -43,22 +49,13 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
 
             foreach (IConfigurationSection section in configuration.GetChildren())
             {
-                if (section.Value is string stringValue2)
+                if (section.Value is string value)
                 {
-                    obj[section.Key!] = stringValue2!;
+                    obj[section.Key] = value;
                 }
             }
         }
 
-        public static void BindCore(IConfiguration configuration, ref Program.MyClass2 obj, BinderOptions? binderOptions)
-        {
-            if (obj is null)
-            {
-                throw new ArgumentNullException(nameof(obj));
-            }
-
-        }
-
         public static void BindCore(IConfiguration configuration, ref Dictionary<string, Program.MyClass2> obj, BinderOptions? binderOptions)
         {
             if (obj is null)
@@ -68,12 +65,11 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
 
             foreach (IConfigurationSection section in configuration.GetChildren())
             {
-                if (!(obj.TryGetValue(section.Key!, out Program.MyClass2? element) && element is not null))
+                if (!(obj.TryGetValue(section.Key, out Program.MyClass2? element) && element is not null))
                 {
                     element = new Program.MyClass2();
                 }
-                BindCore(section, ref element!, binderOptions);
-                obj[section.Key!] = element;
+                obj[section.Key] = element;
             }
         }
 
@@ -84,88 +80,74 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
                 throw new ArgumentNullException(nameof(obj));
             }
 
-            List<string>? temp = null;
-            foreach (IConfigurationSection section in configuration.GetChildren())
+            ValidateConfigurationKeys(typeof(Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions);
+
+            obj.MyString = configuration["MyString"]!;
+
+            if (configuration["MyInt"] is string value1)
             {
-                switch (section.Key)
-                {
-                    case "MyString":
-                        {
-                            obj.MyString = configuration["MyString"]!;
-                        }
-                        break;
-                    case "MyInt":
-                        {
-                            if (configuration["MyInt"] is string stringValue6)
-                            {
-                                obj.MyInt = ParseInt(stringValue6, () => section.Path)!;
-                            }
-                        }
-                        break;
-                    case "MyList":
-                        {
-                            if (HasChildren(section))
-                            {
-                                List<int> temp7 = obj.MyList;
-                                temp7 ??= new List<int>();
-                                BindCore(section, ref temp7, binderOptions);
-                                obj.MyList = temp7;
-                            }
-                        }
-                        break;
-                    case "MyDictionary":
-                        {
-                            if (HasChildren(section))
-                            {
-                                Dictionary<string, string> temp8 = obj.MyDictionary;
-                                temp8 ??= new Dictionary<string, string>();
-                                BindCore(section, ref temp8, binderOptions);
-                                obj.MyDictionary = temp8;
-                            }
-                        }
-                        break;
-                    case "MyComplexDictionary":
-                        {
-                            if (HasChildren(section))
-                            {
-                                Dictionary<string, Program.MyClass2> temp9 = obj.MyComplexDictionary;
-                                temp9 ??= new Dictionary<string, Program.MyClass2>();
-                                BindCore(section, ref temp9, binderOptions);
-                                obj.MyComplexDictionary = temp9;
-                            }
-                        }
-                        break;
-                    default:
-                        {
-                            if (binderOptions?.ErrorOnUnknownConfiguration == true)
-                            {
-                                (temp ??= new List<string>()).Add($"'{section.Key}'");
-                            }
-                        }
-                        break;
-                }
+                obj.MyInt = ParseInt(value1, () => configuration.GetSection("MyInt").Path);
+            }
+
+            if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section2)
+            {
+                List<int> temp4 = obj.MyList;
+                temp4 ??= new List<int>();
+                BindCore(section2, ref temp4, binderOptions);
+                obj.MyList = temp4;
+            }
+
+            if (AsConfigWithChildren(configuration.GetSection("MyDictionary")) is IConfigurationSection section5)
+            {
+                Dictionary<string, string> temp7 = obj.MyDictionary;
+                temp7 ??= new Dictionary<string, string>();
+                BindCore(section5, ref temp7, binderOptions);
+                obj.MyDictionary = temp7;
             }
 
-            if (temp is not null)
+            if (AsConfigWithChildren(configuration.GetSection("MyComplexDictionary")) is IConfigurationSection section8)
             {
-                throw new InvalidOperationException($"'ErrorOnUnknownConfiguration' was set on the provided BinderOptions, but the following properties were not found on the instance of {typeof(Program.MyClass)}: {string.Join(", ", temp)}");
+                Dictionary<string, Program.MyClass2> temp10 = obj.MyComplexDictionary;
+                temp10 ??= new Dictionary<string, Program.MyClass2>();
+                BindCore(section8, ref temp10, binderOptions);
+                obj.MyComplexDictionary = temp10;
             }
         }
 
-        public static bool HasChildren(IConfiguration configuration)
+        /// <summary>If required by the binder options, validates that there are no unknown keys in the input configuration object.</summary>
+        public static void ValidateConfigurationKeys(Type type, Lazy<HashSet<string>> keys, IConfiguration configuration, BinderOptions? binderOptions)
         {
-            foreach (IConfigurationSection section in configuration.GetChildren())
+            if (binderOptions?.ErrorOnUnknownConfiguration is true)
+            {
+                List<string>? temp = null;
+                foreach (IConfigurationSection section in configuration.GetChildren())
+                {
+                    if (!keys.Value.Contains(section.Key))
+                    {
+                        (temp ??= new List<string>()).Add($"'{section.Key}'");
+                    }
+                }
+                if (temp is not null)
+                {
+                    throw new InvalidOperationException($"'ErrorOnUnknownConfiguration' was set on the provided BinderOptions, but the following properties were not found on the instance of {type}: {string.Join(", ", temp)}");
+                }
+            }
+        }
+
+        public static IConfiguration? AsConfigWithChildren(IConfiguration configuration)
+        {
+            foreach (IConfigurationSection _ in configuration.GetChildren())
             {
-                return true;
+                return configuration;
             }
-            return false;
+            return null;
         }
 
-        public static int ParseInt(string stringValue, Func<string?> getPath)
+        public static int ParseInt(string value, Func<string?> getPath)
         {
             try
             {
-                return int.Parse(stringValue, NumberStyles.Integer, CultureInfo.InvariantCulture);
+                return int.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture);
             }
             catch (Exception exception)
             {
index ea9c6ba..edc299c 100644 (file)
@@ -1,7 +1,9 @@
 // <auto-generated/>
 #nullable enable
+#pragma warning disable CS0612, CS0618 // Suppress warnings about [Obsolete] member usage in generated code.
 
 /// <summary>Generated helper providing an AOT and linking compatible implementation for configuration binding.</summary>
+[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")]
 internal static class GeneratedConfigurationBinder
 {
     /// <summary>Attempts to bind the configuration instance to a new instance of type T.</summary>
@@ -21,12 +23,17 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
 {
     using Microsoft.Extensions.Configuration;
     using System;
+    using System.CodeDom.Compiler;
     using System.Collections.Generic;
     using System.Globalization;
 
     /// <summary>Provide core binding logic.</summary>
-    internal static class CoreBindingHelper
+    [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")]
+    file static class CoreBindingHelper
     {
+        private readonly static Lazy<HashSet<string>> s_configKeys_ProgramMyClass = new(() => new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "MyString", "MyInt", "MyList", "MyArray", "MyDictionary" });
+        private readonly static Lazy<HashSet<string>> s_configKeys_ProgramMyClass2 = new(() => new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "MyInt" });
+
         public static object? GetCore(this IConfiguration configuration, Type type, Action<BinderOptions>? configureOptions)
         {
             if (configuration is null)
@@ -67,9 +74,9 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
 
             foreach (IConfigurationSection section in configuration.GetChildren())
             {
-                if (section.Value is string stringValue2)
+                if (section.Value is string value)
                 {
-                    obj.Add(ParseInt(stringValue2, () => section.Path)!);
+                    obj.Add(ParseInt(value, () => section.Path));
                 }
             }
         }
@@ -81,11 +88,11 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
                 throw new ArgumentNullException(nameof(obj));
             }
 
-            var temp3 = new List<int>();
-            BindCore(configuration, ref temp3, binderOptions);
+            var temp2 = new List<int>();
+            BindCore(configuration, ref temp2, binderOptions);
             int originalCount = obj.Length;
-            Array.Resize(ref obj, originalCount + temp3.Count);
-            temp3.CopyTo(obj, originalCount);
+            Array.Resize(ref obj, originalCount + temp2.Count);
+            temp2.CopyTo(obj, originalCount);
         }
 
         public static void BindCore(IConfiguration configuration, ref Dictionary<string, string> obj, BinderOptions? binderOptions)
@@ -97,9 +104,9 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
 
             foreach (IConfigurationSection section in configuration.GetChildren())
             {
-                if (section.Value is string stringValue6)
+                if (section.Value is string value)
                 {
-                    obj[section.Key!] = stringValue6!;
+                    obj[section.Key] = value;
                 }
             }
         }
@@ -111,71 +118,37 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
                 throw new ArgumentNullException(nameof(obj));
             }
 
-            List<string>? temp = null;
-            foreach (IConfigurationSection section in configuration.GetChildren())
+            ValidateConfigurationKeys(typeof(Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions);
+
+            obj.MyString = configuration["MyString"]!;
+
+            if (configuration["MyInt"] is string value5)
             {
-                switch (section.Key)
-                {
-                    case "MyString":
-                        {
-                            obj.MyString = configuration["MyString"]!;
-                        }
-                        break;
-                    case "MyInt":
-                        {
-                            if (configuration["MyInt"] is string stringValue8)
-                            {
-                                obj.MyInt = ParseInt(stringValue8, () => section.Path)!;
-                            }
-                        }
-                        break;
-                    case "MyList":
-                        {
-                            if (HasChildren(section))
-                            {
-                                List<int> temp9 = obj.MyList;
-                                temp9 ??= new List<int>();
-                                BindCore(section, ref temp9, binderOptions);
-                                obj.MyList = temp9;
-                            }
-                        }
-                        break;
-                    case "MyArray":
-                        {
-                            if (HasChildren(section))
-                            {
-                                int[] temp10 = obj.MyArray;
-                                temp10 ??= new int[0];
-                                BindCore(section, ref temp10, binderOptions);
-                                obj.MyArray = temp10;
-                            }
-                        }
-                        break;
-                    case "MyDictionary":
-                        {
-                            if (HasChildren(section))
-                            {
-                                Dictionary<string, string> temp11 = obj.MyDictionary;
-                                temp11 ??= new Dictionary<string, string>();
-                                BindCore(section, ref temp11, binderOptions);
-                                obj.MyDictionary = temp11;
-                            }
-                        }
-                        break;
-                    default:
-                        {
-                            if (binderOptions?.ErrorOnUnknownConfiguration == true)
-                            {
-                                (temp ??= new List<string>()).Add($"'{section.Key}'");
-                            }
-                        }
-                        break;
-                }
+                obj.MyInt = ParseInt(value5, () => configuration.GetSection("MyInt").Path);
+            }
+
+            if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section6)
+            {
+                List<int> temp8 = obj.MyList;
+                temp8 ??= new List<int>();
+                BindCore(section6, ref temp8, binderOptions);
+                obj.MyList = temp8;
+            }
+
+            if (AsConfigWithChildren(configuration.GetSection("MyArray")) is IConfigurationSection section9)
+            {
+                int[] temp11 = obj.MyArray;
+                temp11 ??= new int[0];
+                BindCore(section9, ref temp11, binderOptions);
+                obj.MyArray = temp11;
             }
 
-            if (temp is not null)
+            if (AsConfigWithChildren(configuration.GetSection("MyDictionary")) is IConfigurationSection section12)
             {
-                throw new InvalidOperationException($"'ErrorOnUnknownConfiguration' was set on the provided BinderOptions, but the following properties were not found on the instance of {typeof(Program.MyClass)}: {string.Join(", ", temp)}");
+                Dictionary<string, string> temp14 = obj.MyDictionary;
+                temp14 ??= new Dictionary<string, string>();
+                BindCore(section12, ref temp14, binderOptions);
+                obj.MyDictionary = temp14;
             }
         }
 
@@ -186,33 +159,31 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
                 throw new ArgumentNullException(nameof(obj));
             }
 
-            List<string>? temp = null;
-            foreach (IConfigurationSection section in configuration.GetChildren())
+            ValidateConfigurationKeys(typeof(Program.MyClass2), s_configKeys_ProgramMyClass2, configuration, binderOptions);
+
+            if (configuration["MyInt"] is string value15)
             {
-                switch (section.Key)
-                {
-                    case "MyInt":
-                        {
-                            if (configuration["MyInt"] is string stringValue12)
-                            {
-                                obj.MyInt = ParseInt(stringValue12, () => section.Path)!;
-                            }
-                        }
-                        break;
-                    default:
-                        {
-                            if (binderOptions?.ErrorOnUnknownConfiguration == true)
-                            {
-                                (temp ??= new List<string>()).Add($"'{section.Key}'");
-                            }
-                        }
-                        break;
-                }
+                obj.MyInt = ParseInt(value15, () => configuration.GetSection("MyInt").Path);
             }
+        }
 
-            if (temp is not null)
+        /// <summary>If required by the binder options, validates that there are no unknown keys in the input configuration object.</summary>
+        public static void ValidateConfigurationKeys(Type type, Lazy<HashSet<string>> keys, IConfiguration configuration, BinderOptions? binderOptions)
+        {
+            if (binderOptions?.ErrorOnUnknownConfiguration is true)
             {
-                throw new InvalidOperationException($"'ErrorOnUnknownConfiguration' was set on the provided BinderOptions, but the following properties were not found on the instance of {typeof(Program.MyClass2)}: {string.Join(", ", temp)}");
+                List<string>? temp = null;
+                foreach (IConfigurationSection section in configuration.GetChildren())
+                {
+                    if (!keys.Value.Contains(section.Key))
+                    {
+                        (temp ??= new List<string>()).Add($"'{section.Key}'");
+                    }
+                }
+                if (temp is not null)
+                {
+                    throw new InvalidOperationException($"'ErrorOnUnknownConfiguration' was set on the provided BinderOptions, but the following properties were not found on the instance of {type}: {string.Join(", ", temp)}");
+                }
             }
         }
 
@@ -222,16 +193,16 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
             {
                 return true;
             }
-            return HasChildren(configuration);
+            return AsConfigWithChildren(configuration) is not null;
         }
 
-        public static bool HasChildren(IConfiguration configuration)
+        public static IConfiguration? AsConfigWithChildren(IConfiguration configuration)
         {
-            foreach (IConfigurationSection section in configuration.GetChildren())
+            foreach (IConfigurationSection _ in configuration.GetChildren())
             {
-                return true;
+                return configuration;
             }
-            return false;
+            return null;
         }
 
         public static BinderOptions? GetBinderOptions(Action<BinderOptions>? configureOptions)
@@ -249,11 +220,11 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
             return binderOptions;
         }
 
-        public static int ParseInt(string stringValue, Func<string?> getPath)
+        public static int ParseInt(string value, Func<string?> getPath)
         {
             try
             {
-                return int.Parse(stringValue, NumberStyles.Integer, CultureInfo.InvariantCulture);
+                return int.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture);
             }
             catch (Exception exception)
             {
index 2d5cb75..c9d49fa 100644 (file)
@@ -1,7 +1,9 @@
 // <auto-generated/>
 #nullable enable
+#pragma warning disable CS0612, CS0618 // Suppress warnings about [Obsolete] member usage in generated code.
 
 /// <summary>Generated helper providing an AOT and linking compatible implementation for configuration binding.</summary>
+[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")]
 internal static class GeneratedConfigurationBinder
 {
     /// <summary>Extracts the value with the specified key and converts it to the specified type.</summary>
@@ -21,10 +23,12 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
 {
     using Microsoft.Extensions.Configuration;
     using System;
+    using System.CodeDom.Compiler;
     using System.Globalization;
 
     /// <summary>Provide core binding logic.</summary>
-    internal static class CoreBindingHelper
+    [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")]
+    file static class CoreBindingHelper
     {
         public static object? GetValueCore(this IConfiguration configuration, Type type, string key)
         {
@@ -35,46 +39,39 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
 
             IConfigurationSection section = configuration.GetSection(key);
 
+            if (section.Value is not string value)
+            {
+                return null;
+            }
+
             if (type == typeof(int))
             {
-                if (section.Value is string stringValue0)
-                {
-                    return ParseInt(stringValue0, () => section.Path);
-                }
+                return ParseInt(value, () => section.Path);
             }
 
             if (type == typeof(bool?))
             {
-                if (section.Value is string stringValue1)
-                {
-                    return ParseBool(stringValue1, () => section.Path);
-                }
+                return ParseBool(value, () => section.Path);
             }
 
             if (type == typeof(byte[]))
             {
-                if (section.Value is string stringValue2)
-                {
-                    return ParseByteArray(stringValue2, () => section.Path);
-                }
+                return ParseByteArray(value, () => section.Path);
             }
 
             if (type == typeof(CultureInfo))
             {
-                if (section.Value is string stringValue3)
-                {
-                    return ParseCultureInfo(stringValue3, () => section.Path);
-                }
+                return ParseCultureInfo(value, () => section.Path);
             }
 
             return null;
         }
 
-        public static int ParseInt(string stringValue, Func<string?> getPath)
+        public static int ParseInt(string value, Func<string?> getPath)
         {
             try
             {
-                return int.Parse(stringValue, NumberStyles.Integer, CultureInfo.InvariantCulture);
+                return int.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture);
             }
             catch (Exception exception)
             {
@@ -82,11 +79,11 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
             }
         }
 
-        public static bool ParseBool(string stringValue, Func<string?> getPath)
+        public static bool ParseBool(string value, Func<string?> getPath)
         {
             try
             {
-                return bool.Parse(stringValue);
+                return bool.Parse(value);
             }
             catch (Exception exception)
             {
@@ -94,11 +91,11 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
             }
         }
 
-        public static byte[] ParseByteArray(string stringValue, Func<string?> getPath)
+        public static byte[] ParseByteArray(string value, Func<string?> getPath)
         {
             try
             {
-                return Convert.FromBase64String(stringValue);
+                return Convert.FromBase64String(value);
             }
             catch (Exception exception)
             {
@@ -106,11 +103,11 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
             }
         }
 
-        public static CultureInfo ParseCultureInfo(string stringValue, Func<string?> getPath)
+        public static CultureInfo ParseCultureInfo(string value, Func<string?> getPath)
         {
             try
             {
-                return CultureInfo.GetCultureInfo(stringValue);
+                return CultureInfo.GetCultureInfo(value);
             }
             catch (Exception exception)
             {
index 19ecaa0..17c963b 100644 (file)
@@ -1,7 +1,9 @@
 // <auto-generated/>
 #nullable enable
+#pragma warning disable CS0612, CS0618 // Suppress warnings about [Obsolete] member usage in generated code.
 
 /// <summary>Generated helper providing an AOT and linking compatible implementation for configuration binding.</summary>
+[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")]
 internal static class GeneratedConfigurationBinder
 {
     /// <summary>Extracts the value with the specified key and converts it to the specified type.</summary>
@@ -12,10 +14,12 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
 {
     using Microsoft.Extensions.Configuration;
     using System;
+    using System.CodeDom.Compiler;
     using System.Globalization;
 
     /// <summary>Provide core binding logic.</summary>
-    internal static class CoreBindingHelper
+    [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")]
+    file static class CoreBindingHelper
     {
         public static object? GetValueCore(this IConfiguration configuration, Type type, string key)
         {
@@ -26,22 +30,24 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
 
             IConfigurationSection section = configuration.GetSection(key);
 
+            if (section.Value is not string value)
+            {
+                return null;
+            }
+
             if (type == typeof(int))
             {
-                if (section.Value is string stringValue0)
-                {
-                    return ParseInt(stringValue0, () => section.Path);
-                }
+                return ParseInt(value, () => section.Path);
             }
 
             return null;
         }
 
-        public static int ParseInt(string stringValue, Func<string?> getPath)
+        public static int ParseInt(string value, Func<string?> getPath)
         {
             try
             {
-                return int.Parse(stringValue, NumberStyles.Integer, CultureInfo.InvariantCulture);
+                return int.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture);
             }
             catch (Exception exception)
             {
index dac9192..1148109 100644 (file)
@@ -1,7 +1,9 @@
 // <auto-generated/>
 #nullable enable
+#pragma warning disable CS0612, CS0618 // Suppress warnings about [Obsolete] member usage in generated code.
 
 /// <summary>Generated helper providing an AOT and linking compatible implementation for configuration binding.</summary>
+[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")]
 internal static class GeneratedConfigurationBinder
 {
     /// <summary>Extracts the value with the specified key and converts it to the specified type.</summary>
@@ -12,10 +14,12 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
 {
     using Microsoft.Extensions.Configuration;
     using System;
+    using System.CodeDom.Compiler;
     using System.Globalization;
 
     /// <summary>Provide core binding logic.</summary>
-    internal static class CoreBindingHelper
+    [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")]
+    file static class CoreBindingHelper
     {
         public static object? GetValueCore(this IConfiguration configuration, Type type, string key)
         {
@@ -26,22 +30,24 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
 
             IConfigurationSection section = configuration.GetSection(key);
 
+            if (section.Value is not string value)
+            {
+                return null;
+            }
+
             if (type == typeof(int))
             {
-                if (section.Value is string stringValue0)
-                {
-                    return ParseInt(stringValue0, () => section.Path);
-                }
+                return ParseInt(value, () => section.Path);
             }
 
             return null;
         }
 
-        public static int ParseInt(string stringValue, Func<string?> getPath)
+        public static int ParseInt(string value, Func<string?> getPath)
         {
             try
             {
-                return int.Parse(stringValue, NumberStyles.Integer, CultureInfo.InvariantCulture);
+                return int.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture);
             }
             catch (Exception exception)
             {
index 4e7dbe7..c833b20 100644 (file)
@@ -1,7 +1,9 @@
 // <auto-generated/>
 #nullable enable
+#pragma warning disable CS0612, CS0618 // Suppress warnings about [Obsolete] member usage in generated code.
 
 /// <summary>Generated helper providing an AOT and linking compatible implementation for configuration binding.</summary>
+[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")]
 internal static class GeneratedConfigurationBinder
 {
     /// <summary>Extracts the value with the specified key and converts it to the specified type.</summary>
@@ -12,10 +14,12 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
 {
     using Microsoft.Extensions.Configuration;
     using System;
+    using System.CodeDom.Compiler;
     using System.Globalization;
 
     /// <summary>Provide core binding logic.</summary>
-    internal static class CoreBindingHelper
+    [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")]
+    file static class CoreBindingHelper
     {
         public static object? GetValueCore(this IConfiguration configuration, Type type, string key)
         {
@@ -26,22 +30,24 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
 
             IConfigurationSection section = configuration.GetSection(key);
 
+            if (section.Value is not string value)
+            {
+                return null;
+            }
+
             if (type == typeof(bool?))
             {
-                if (section.Value is string stringValue0)
-                {
-                    return ParseBool(stringValue0, () => section.Path);
-                }
+                return ParseBool(value, () => section.Path);
             }
 
             return null;
         }
 
-        public static bool ParseBool(string stringValue, Func<string?> getPath)
+        public static bool ParseBool(string value, Func<string?> getPath)
         {
             try
             {
-                return bool.Parse(stringValue);
+                return bool.Parse(value);
             }
             catch (Exception exception)
             {
index e7eb6cc..f773f79 100644 (file)
@@ -1,7 +1,9 @@
 // <auto-generated/>
 #nullable enable
+#pragma warning disable CS0612, CS0618 // Suppress warnings about [Obsolete] member usage in generated code.
 
 /// <summary>Generated helper providing an AOT and linking compatible implementation for configuration binding.</summary>
+[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")]
 internal static class GeneratedConfigurationBinder
 {
     /// <summary>Extracts the value with the specified key and converts it to the specified type.</summary>
@@ -11,10 +13,13 @@ internal static class GeneratedConfigurationBinder
 namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
 {
     using Microsoft.Extensions.Configuration;
+    using System;
+    using System.CodeDom.Compiler;
     using System.Globalization;
 
     /// <summary>Provide core binding logic.</summary>
-    internal static class CoreBindingHelper
+    [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")]
+    file static class CoreBindingHelper
     {
         public static object? GetValueCore(this IConfiguration configuration, Type type, string key)
         {
@@ -25,22 +30,24 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
 
             IConfigurationSection section = configuration.GetSection(key);
 
+            if (section.Value is not string value)
+            {
+                return null;
+            }
+
             if (type == typeof(CultureInfo))
             {
-                if (section.Value is string stringValue0)
-                {
-                    return ParseCultureInfo(stringValue0, () => section.Path);
-                }
+                return ParseCultureInfo(value, () => section.Path);
             }
 
             return null;
         }
 
-        public static CultureInfo ParseCultureInfo(string stringValue, Func<string?> getPath)
+        public static CultureInfo ParseCultureInfo(string value, Func<string?> getPath)
         {
             try
             {
-                return CultureInfo.GetCultureInfo(stringValue);
+                return CultureInfo.GetCultureInfo(value);
             }
             catch (Exception exception)
             {
index 8bfc62e..92768a5 100644 (file)
@@ -1,7 +1,9 @@
-// <auto-generated/>
+// <auto-generated/>
 #nullable enable
+#pragma warning disable CS0612, CS0618 // Suppress warnings about [Obsolete] member usage in generated code.
 
 /// <summary>Generated helper providing an AOT and linking compatible implementation for configuration binding.</summary>
+[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")]
 internal static class GeneratedConfigurationBinder
 {
     /// <summary>Attempts to bind the configuration instance to a new instance of type T.</summary>
@@ -12,12 +14,16 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
 {
     using Microsoft.Extensions.Configuration;
     using System;
+    using System.CodeDom.Compiler;
     using System.Collections.Generic;
     using System.Globalization;
 
     /// <summary>Provide core binding logic.</summary>
-    internal static class CoreBindingHelper
+    [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")]
+    file static class CoreBindingHelper
     {
+        private readonly static Lazy<HashSet<string>> s_configKeys_ProgramMyClass = new(() => new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "MyString", "MyInt", "MyList", "MyArray", "MyDictionary" });
+
         public static object? GetCore(this IConfiguration configuration, Type type, Action<BinderOptions>? configureOptions)
         {
             if (configuration is null)
@@ -51,9 +57,9 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
 
             foreach (IConfigurationSection section in configuration.GetChildren())
             {
-                if (section.Value is string stringValue1)
+                if (section.Value is string value)
                 {
-                    obj.Add(ParseInt(stringValue1, () => section.Path)!);
+                    obj.Add(ParseInt(value, () => section.Path));
                 }
             }
         }
@@ -65,11 +71,11 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
                 throw new ArgumentNullException(nameof(obj));
             }
 
-            var temp2 = new List<int>();
-            BindCore(configuration, ref temp2, binderOptions);
+            var temp1 = new List<int>();
+            BindCore(configuration, ref temp1, binderOptions);
             int originalCount = obj.Length;
-            Array.Resize(ref obj, originalCount + temp2.Count);
-            temp2.CopyTo(obj, originalCount);
+            Array.Resize(ref obj, originalCount + temp1.Count);
+            temp1.CopyTo(obj, originalCount);
         }
 
         public static void BindCore(IConfiguration configuration, ref Dictionary<string, string> obj, BinderOptions? binderOptions)
@@ -81,9 +87,9 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
 
             foreach (IConfigurationSection section in configuration.GetChildren())
             {
-                if (section.Value is string stringValue5)
+                if (section.Value is string value)
                 {
-                    obj[section.Key!] = stringValue5!;
+                    obj[section.Key] = value;
                 }
             }
         }
@@ -95,71 +101,57 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
                 throw new ArgumentNullException(nameof(obj));
             }
 
-            List<string>? temp = null;
-            foreach (IConfigurationSection section in configuration.GetChildren())
+            ValidateConfigurationKeys(typeof(Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions);
+
+            obj.MyString = configuration["MyString"]!;
+
+            if (configuration["MyInt"] is string value4)
             {
-                switch (section.Key)
-                {
-                    case "MyString":
-                        {
-                            obj.MyString = configuration["MyString"]!;
-                        }
-                        break;
-                    case "MyInt":
-                        {
-                            if (configuration["MyInt"] is string stringValue7)
-                            {
-                                obj.MyInt = ParseInt(stringValue7, () => section.Path)!;
-                            }
-                        }
-                        break;
-                    case "MyList":
-                        {
-                            if (HasChildren(section))
-                            {
-                                List<int> temp8 = obj.MyList;
-                                temp8 ??= new List<int>();
-                                BindCore(section, ref temp8, binderOptions);
-                                obj.MyList = temp8;
-                            }
-                        }
-                        break;
-                    case "MyArray":
-                        {
-                            if (HasChildren(section))
-                            {
-                                int[] temp9 = obj.MyArray;
-                                temp9 ??= new int[0];
-                                BindCore(section, ref temp9, binderOptions);
-                                obj.MyArray = temp9;
-                            }
-                        }
-                        break;
-                    case "MyDictionary":
-                        {
-                            if (HasChildren(section))
-                            {
-                                Dictionary<string, string> temp10 = obj.MyDictionary;
-                                temp10 ??= new Dictionary<string, string>();
-                                BindCore(section, ref temp10, binderOptions);
-                                obj.MyDictionary = temp10;
-                            }
-                        }
-                        break;
-                    default:
-                        {
-                            if (binderOptions?.ErrorOnUnknownConfiguration == true)
-                            {
-                                (temp ??= new List<string>()).Add($"'{section.Key}'");
-                            }
-                        }
-                        break;
-                }
+                obj.MyInt = ParseInt(value4, () => configuration.GetSection("MyInt").Path);
+            }
+
+            if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section5)
+            {
+                List<int> temp7 = obj.MyList;
+                temp7 ??= new List<int>();
+                BindCore(section5, ref temp7, binderOptions);
+                obj.MyList = temp7;
+            }
+
+            if (AsConfigWithChildren(configuration.GetSection("MyArray")) is IConfigurationSection section8)
+            {
+                int[] temp10 = obj.MyArray;
+                temp10 ??= new int[0];
+                BindCore(section8, ref temp10, binderOptions);
+                obj.MyArray = temp10;
             }
 
-            if (temp is not null)
+            if (AsConfigWithChildren(configuration.GetSection("MyDictionary")) is IConfigurationSection section11)
             {
-                throw new InvalidOperationException($"'ErrorOnUnknownConfiguration' was set on the provided BinderOptions, but the following properties were not found on the instance of {typeof(Program.MyClass)}: {string.Join(", ", temp)}");
+                Dictionary<string, string> temp13 = obj.MyDictionary;
+                temp13 ??= new Dictionary<string, string>();
+                BindCore(section11, ref temp13, binderOptions);
+                obj.MyDictionary = temp13;
+            }
+        }
+
+        /// <summary>If required by the binder options, validates that there are no unknown keys in the input configuration object.</summary>
+        public static void ValidateConfigurationKeys(Type type, Lazy<HashSet<string>> keys, IConfiguration configuration, BinderOptions? binderOptions)
+        {
+            if (binderOptions?.ErrorOnUnknownConfiguration is true)
+            {
+                List<string>? temp = null;
+                foreach (IConfigurationSection section in configuration.GetChildren())
+                {
+                    if (!keys.Value.Contains(section.Key))
+                    {
+                        (temp ??= new List<string>()).Add($"'{section.Key}'");
+                    }
+                }
+                if (temp is not null)
+                {
+                    throw new InvalidOperationException($"'ErrorOnUnknownConfiguration' was set on the provided BinderOptions, but the following properties were not found on the instance of {type}: {string.Join(", ", temp)}");
+                }
             }
         }
 
@@ -169,16 +161,16 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
             {
                 return true;
             }
-            return HasChildren(configuration);
+            return AsConfigWithChildren(configuration) is not null;
         }
 
-        public static bool HasChildren(IConfiguration configuration)
+        public static IConfiguration? AsConfigWithChildren(IConfiguration configuration)
         {
-            foreach (IConfigurationSection section in configuration.GetChildren())
+            foreach (IConfigurationSection _ in configuration.GetChildren())
             {
-                return true;
+                return configuration;
             }
-            return false;
+            return null;
         }
 
         public static BinderOptions? GetBinderOptions(Action<BinderOptions>? configureOptions)
@@ -196,11 +188,11 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
             return binderOptions;
         }
 
-        public static int ParseInt(string stringValue, Func<string?> getPath)
+        public static int ParseInt(string value, Func<string?> getPath)
         {
             try
             {
-                return int.Parse(stringValue, NumberStyles.Integer, CultureInfo.InvariantCulture);
+                return int.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture);
             }
             catch (Exception exception)
             {
index 72d5f02..528049d 100644 (file)
@@ -1,7 +1,9 @@
 // <auto-generated/>
 #nullable enable
+#pragma warning disable CS0612, CS0618 // Suppress warnings about [Obsolete] member usage in generated code.
 
 /// <summary>Generated helper providing an AOT and linking compatible implementation for configuration binding.</summary>
+[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")]
 internal static class GeneratedConfigurationBinder
 {
     /// <summary>Attempts to bind the configuration instance to a new instance of type T.</summary>
@@ -12,12 +14,16 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
 {
     using Microsoft.Extensions.Configuration;
     using System;
+    using System.CodeDom.Compiler;
     using System.Collections.Generic;
     using System.Globalization;
 
     /// <summary>Provide core binding logic.</summary>
-    internal static class CoreBindingHelper
+    [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")]
+    file static class CoreBindingHelper
     {
+        private readonly static Lazy<HashSet<string>> s_configKeys_ProgramMyClass = new(() => new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "MyString", "MyInt", "MyList", "MyArray", "MyDictionary" });
+
         public static object? GetCore(this IConfiguration configuration, Type type, Action<BinderOptions>? configureOptions)
         {
             if (configuration is null)
@@ -51,9 +57,9 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
 
             foreach (IConfigurationSection section in configuration.GetChildren())
             {
-                if (section.Value is string stringValue1)
+                if (section.Value is string value)
                 {
-                    obj.Add(ParseInt(stringValue1, () => section.Path)!);
+                    obj.Add(ParseInt(value, () => section.Path));
                 }
             }
         }
@@ -65,11 +71,11 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
                 throw new ArgumentNullException(nameof(obj));
             }
 
-            var temp2 = new List<int>();
-            BindCore(configuration, ref temp2, binderOptions);
+            var temp1 = new List<int>();
+            BindCore(configuration, ref temp1, binderOptions);
             int originalCount = obj.Length;
-            Array.Resize(ref obj, originalCount + temp2.Count);
-            temp2.CopyTo(obj, originalCount);
+            Array.Resize(ref obj, originalCount + temp1.Count);
+            temp1.CopyTo(obj, originalCount);
         }
 
         public static void BindCore(IConfiguration configuration, ref Dictionary<string, string> obj, BinderOptions? binderOptions)
@@ -81,9 +87,9 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
 
             foreach (IConfigurationSection section in configuration.GetChildren())
             {
-                if (section.Value is string stringValue5)
+                if (section.Value is string value)
                 {
-                    obj[section.Key!] = stringValue5!;
+                    obj[section.Key] = value;
                 }
             }
         }
@@ -95,71 +101,57 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
                 throw new ArgumentNullException(nameof(obj));
             }
 
-            List<string>? temp = null;
-            foreach (IConfigurationSection section in configuration.GetChildren())
+            ValidateConfigurationKeys(typeof(Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions);
+
+            obj.MyString = configuration["MyString"]!;
+
+            if (configuration["MyInt"] is string value4)
             {
-                switch (section.Key)
-                {
-                    case "MyString":
-                        {
-                            obj.MyString = configuration["MyString"]!;
-                        }
-                        break;
-                    case "MyInt":
-                        {
-                            if (configuration["MyInt"] is string stringValue7)
-                            {
-                                obj.MyInt = ParseInt(stringValue7, () => section.Path)!;
-                            }
-                        }
-                        break;
-                    case "MyList":
-                        {
-                            if (HasChildren(section))
-                            {
-                                List<int> temp8 = obj.MyList;
-                                temp8 ??= new List<int>();
-                                BindCore(section, ref temp8, binderOptions);
-                                obj.MyList = temp8;
-                            }
-                        }
-                        break;
-                    case "MyArray":
-                        {
-                            if (HasChildren(section))
-                            {
-                                int[] temp9 = obj.MyArray;
-                                temp9 ??= new int[0];
-                                BindCore(section, ref temp9, binderOptions);
-                                obj.MyArray = temp9;
-                            }
-                        }
-                        break;
-                    case "MyDictionary":
-                        {
-                            if (HasChildren(section))
-                            {
-                                Dictionary<string, string> temp10 = obj.MyDictionary;
-                                temp10 ??= new Dictionary<string, string>();
-                                BindCore(section, ref temp10, binderOptions);
-                                obj.MyDictionary = temp10;
-                            }
-                        }
-                        break;
-                    default:
-                        {
-                            if (binderOptions?.ErrorOnUnknownConfiguration == true)
-                            {
-                                (temp ??= new List<string>()).Add($"'{section.Key}'");
-                            }
-                        }
-                        break;
-                }
+                obj.MyInt = ParseInt(value4, () => configuration.GetSection("MyInt").Path);
+            }
+
+            if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section5)
+            {
+                List<int> temp7 = obj.MyList;
+                temp7 ??= new List<int>();
+                BindCore(section5, ref temp7, binderOptions);
+                obj.MyList = temp7;
+            }
+
+            if (AsConfigWithChildren(configuration.GetSection("MyArray")) is IConfigurationSection section8)
+            {
+                int[] temp10 = obj.MyArray;
+                temp10 ??= new int[0];
+                BindCore(section8, ref temp10, binderOptions);
+                obj.MyArray = temp10;
             }
 
-            if (temp is not null)
+            if (AsConfigWithChildren(configuration.GetSection("MyDictionary")) is IConfigurationSection section11)
             {
-                throw new InvalidOperationException($"'ErrorOnUnknownConfiguration' was set on the provided BinderOptions, but the following properties were not found on the instance of {typeof(Program.MyClass)}: {string.Join(", ", temp)}");
+                Dictionary<string, string> temp13 = obj.MyDictionary;
+                temp13 ??= new Dictionary<string, string>();
+                BindCore(section11, ref temp13, binderOptions);
+                obj.MyDictionary = temp13;
+            }
+        }
+
+        /// <summary>If required by the binder options, validates that there are no unknown keys in the input configuration object.</summary>
+        public static void ValidateConfigurationKeys(Type type, Lazy<HashSet<string>> keys, IConfiguration configuration, BinderOptions? binderOptions)
+        {
+            if (binderOptions?.ErrorOnUnknownConfiguration is true)
+            {
+                List<string>? temp = null;
+                foreach (IConfigurationSection section in configuration.GetChildren())
+                {
+                    if (!keys.Value.Contains(section.Key))
+                    {
+                        (temp ??= new List<string>()).Add($"'{section.Key}'");
+                    }
+                }
+                if (temp is not null)
+                {
+                    throw new InvalidOperationException($"'ErrorOnUnknownConfiguration' was set on the provided BinderOptions, but the following properties were not found on the instance of {type}: {string.Join(", ", temp)}");
+                }
             }
         }
 
@@ -169,16 +161,16 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
             {
                 return true;
             }
-            return HasChildren(configuration);
+            return AsConfigWithChildren(configuration) is not null;
         }
 
-        public static bool HasChildren(IConfiguration configuration)
+        public static IConfiguration? AsConfigWithChildren(IConfiguration configuration)
         {
-            foreach (IConfigurationSection section in configuration.GetChildren())
+            foreach (IConfigurationSection _ in configuration.GetChildren())
             {
-                return true;
+                return configuration;
             }
-            return false;
+            return null;
         }
 
         public static BinderOptions? GetBinderOptions(Action<BinderOptions>? configureOptions)
@@ -196,11 +188,11 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
             return binderOptions;
         }
 
-        public static int ParseInt(string stringValue, Func<string?> getPath)
+        public static int ParseInt(string value, Func<string?> getPath)
         {
             try
             {
-                return int.Parse(stringValue, NumberStyles.Integer, CultureInfo.InvariantCulture);
+                return int.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture);
             }
             catch (Exception exception)
             {
index 65cd132..01a865f 100644 (file)
@@ -1,7 +1,9 @@
 // <auto-generated/>
 #nullable enable
+#pragma warning disable CS0612, CS0618 // Suppress warnings about [Obsolete] member usage in generated code.
 
 /// <summary>Generated helper providing an AOT and linking compatible implementation for configuration binding.</summary>
+[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")]
 internal static class GeneratedConfigurationBinder
 {
     /// <summary>Attempts to bind the configuration instance to a new instance of type T.</summary>
@@ -12,12 +14,16 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
 {
     using Microsoft.Extensions.Configuration;
     using System;
+    using System.CodeDom.Compiler;
     using System.Collections.Generic;
     using System.Globalization;
 
     /// <summary>Provide core binding logic.</summary>
-    internal static class CoreBindingHelper
+    [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")]
+    file static class CoreBindingHelper
     {
+        private readonly static Lazy<HashSet<string>> s_configKeys_ProgramMyClass2 = new(() => new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "MyInt" });
+
         public static object? GetCore(this IConfiguration configuration, Type type, Action<BinderOptions>? configureOptions)
         {
             if (configuration is null)
@@ -49,33 +55,31 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
                 throw new ArgumentNullException(nameof(obj));
             }
 
-            List<string>? temp = null;
-            foreach (IConfigurationSection section in configuration.GetChildren())
+            ValidateConfigurationKeys(typeof(Program.MyClass2), s_configKeys_ProgramMyClass2, configuration, binderOptions);
+
+            if (configuration["MyInt"] is string value1)
             {
-                switch (section.Key)
-                {
-                    case "MyInt":
-                        {
-                            if (configuration["MyInt"] is string stringValue1)
-                            {
-                                obj.MyInt = ParseInt(stringValue1, () => section.Path)!;
-                            }
-                        }
-                        break;
-                    default:
-                        {
-                            if (binderOptions?.ErrorOnUnknownConfiguration == true)
-                            {
-                                (temp ??= new List<string>()).Add($"'{section.Key}'");
-                            }
-                        }
-                        break;
-                }
+                obj.MyInt = ParseInt(value1, () => configuration.GetSection("MyInt").Path);
             }
+        }
 
-            if (temp is not null)
+        /// <summary>If required by the binder options, validates that there are no unknown keys in the input configuration object.</summary>
+        public static void ValidateConfigurationKeys(Type type, Lazy<HashSet<string>> keys, IConfiguration configuration, BinderOptions? binderOptions)
+        {
+            if (binderOptions?.ErrorOnUnknownConfiguration is true)
             {
-                throw new InvalidOperationException($"'ErrorOnUnknownConfiguration' was set on the provided BinderOptions, but the following properties were not found on the instance of {typeof(Program.MyClass2)}: {string.Join(", ", temp)}");
+                List<string>? temp = null;
+                foreach (IConfigurationSection section in configuration.GetChildren())
+                {
+                    if (!keys.Value.Contains(section.Key))
+                    {
+                        (temp ??= new List<string>()).Add($"'{section.Key}'");
+                    }
+                }
+                if (temp is not null)
+                {
+                    throw new InvalidOperationException($"'ErrorOnUnknownConfiguration' was set on the provided BinderOptions, but the following properties were not found on the instance of {type}: {string.Join(", ", temp)}");
+                }
             }
         }
 
@@ -85,16 +89,16 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
             {
                 return true;
             }
-            return HasChildren(configuration);
+            return AsConfigWithChildren(configuration) is not null;
         }
 
-        public static bool HasChildren(IConfiguration configuration)
+        public static IConfiguration? AsConfigWithChildren(IConfiguration configuration)
         {
-            foreach (IConfigurationSection section in configuration.GetChildren())
+            foreach (IConfigurationSection _ in configuration.GetChildren())
             {
-                return true;
+                return configuration;
             }
-            return false;
+            return null;
         }
 
         public static BinderOptions? GetBinderOptions(Action<BinderOptions>? configureOptions)
@@ -112,11 +116,11 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
             return binderOptions;
         }
 
-        public static int ParseInt(string stringValue, Func<string?> getPath)
+        public static int ParseInt(string value, Func<string?> getPath)
         {
             try
             {
-                return int.Parse(stringValue, NumberStyles.Integer, CultureInfo.InvariantCulture);
+                return int.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture);
             }
             catch (Exception exception)
             {
index f5044a5..620a604 100644 (file)
@@ -1,7 +1,9 @@
 // <auto-generated/>
 #nullable enable
+#pragma warning disable CS0612, CS0618 // Suppress warnings about [Obsolete] member usage in generated code.
 
 /// <summary>Generated helper providing an AOT and linking compatible implementation for configuration binding.</summary>
+[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")]
 internal static class GeneratedConfigurationBinder
 {
     /// <summary>Attempts to bind the configuration instance to a new instance of type T.</summary>
@@ -12,12 +14,16 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
 {
     using Microsoft.Extensions.Configuration;
     using System;
+    using System.CodeDom.Compiler;
     using System.Collections.Generic;
     using System.Globalization;
 
     /// <summary>Provide core binding logic.</summary>
-    internal static class CoreBindingHelper
+    [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")]
+    file static class CoreBindingHelper
     {
+        private readonly static Lazy<HashSet<string>> s_configKeys_ProgramMyClass2 = new(() => new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "MyInt" });
+
         public static object? GetCore(this IConfiguration configuration, Type type, Action<BinderOptions>? configureOptions)
         {
             if (configuration is null)
@@ -49,33 +55,31 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
                 throw new ArgumentNullException(nameof(obj));
             }
 
-            List<string>? temp = null;
-            foreach (IConfigurationSection section in configuration.GetChildren())
+            ValidateConfigurationKeys(typeof(Program.MyClass2), s_configKeys_ProgramMyClass2, configuration, binderOptions);
+
+            if (configuration["MyInt"] is string value1)
             {
-                switch (section.Key)
-                {
-                    case "MyInt":
-                        {
-                            if (configuration["MyInt"] is string stringValue1)
-                            {
-                                obj.MyInt = ParseInt(stringValue1, () => section.Path)!;
-                            }
-                        }
-                        break;
-                    default:
-                        {
-                            if (binderOptions?.ErrorOnUnknownConfiguration == true)
-                            {
-                                (temp ??= new List<string>()).Add($"'{section.Key}'");
-                            }
-                        }
-                        break;
-                }
+                obj.MyInt = ParseInt(value1, () => configuration.GetSection("MyInt").Path);
             }
+        }
 
-            if (temp is not null)
+        /// <summary>If required by the binder options, validates that there are no unknown keys in the input configuration object.</summary>
+        public static void ValidateConfigurationKeys(Type type, Lazy<HashSet<string>> keys, IConfiguration configuration, BinderOptions? binderOptions)
+        {
+            if (binderOptions?.ErrorOnUnknownConfiguration is true)
             {
-                throw new InvalidOperationException($"'ErrorOnUnknownConfiguration' was set on the provided BinderOptions, but the following properties were not found on the instance of {typeof(Program.MyClass2)}: {string.Join(", ", temp)}");
+                List<string>? temp = null;
+                foreach (IConfigurationSection section in configuration.GetChildren())
+                {
+                    if (!keys.Value.Contains(section.Key))
+                    {
+                        (temp ??= new List<string>()).Add($"'{section.Key}'");
+                    }
+                }
+                if (temp is not null)
+                {
+                    throw new InvalidOperationException($"'ErrorOnUnknownConfiguration' was set on the provided BinderOptions, but the following properties were not found on the instance of {type}: {string.Join(", ", temp)}");
+                }
             }
         }
 
@@ -85,16 +89,16 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
             {
                 return true;
             }
-            return HasChildren(configuration);
+            return AsConfigWithChildren(configuration) is not null;
         }
 
-        public static bool HasChildren(IConfiguration configuration)
+        public static IConfiguration? AsConfigWithChildren(IConfiguration configuration)
         {
-            foreach (IConfigurationSection section in configuration.GetChildren())
+            foreach (IConfigurationSection _ in configuration.GetChildren())
             {
-                return true;
+                return configuration;
             }
-            return false;
+            return null;
         }
 
         public static BinderOptions? GetBinderOptions(Action<BinderOptions>? configureOptions)
@@ -112,11 +116,11 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
             return binderOptions;
         }
 
-        public static int ParseInt(string stringValue, Func<string?> getPath)
+        public static int ParseInt(string value, Func<string?> getPath)
         {
             try
             {
-                return int.Parse(stringValue, NumberStyles.Integer, CultureInfo.InvariantCulture);
+                return int.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture);
             }
             catch (Exception exception)
             {
index 4c2a501..2c61207 100644 (file)
@@ -1,7 +1,9 @@
-// <auto-generated/>
+// <auto-generated/>
 #nullable enable
+#pragma warning disable CS0612, CS0618 // Suppress warnings about [Obsolete] member usage in generated code.
 
 /// <summary>Generated helper providing an AOT and linking compatible implementation for configuration binding.</summary>
+[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")]
 internal static class GeneratedOptionsBuilderBinder
 {
     /// <summary>Registers the dependency injection container to bind <typeparamref name="TOptions"/> against the <see cref="global::Microsoft.Extensions.Configuration.IConfiguration"/> obtained from the DI service provider.</summary>
@@ -33,12 +35,16 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
     using Microsoft.Extensions.Configuration;
     using Microsoft.Extensions.DependencyInjection;
     using System;
+    using System.CodeDom.Compiler;
     using System.Collections.Generic;
     using System.Globalization;
 
     /// <summary>Provide core binding logic.</summary>
-    internal static class CoreBindingHelper
+    [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")]
+    file static class CoreBindingHelper
     {
+        private readonly static Lazy<HashSet<string>> s_configKeys_ProgramMyClass = new(() => new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "MyString", "MyInt", "MyList" });
+
         public static void BindCoreUntyped(this IConfiguration configuration, object obj, Type type, Action<BinderOptions>? configureOptions)
         {
             if (configuration is null)
@@ -72,9 +78,9 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
 
             foreach (IConfigurationSection section in configuration.GetChildren())
             {
-                if (section.Value is string stringValue1)
+                if (section.Value is string value)
                 {
-                    obj.Add(ParseInt(stringValue1, () => section.Path)!);
+                    obj.Add(ParseInt(value, () => section.Path));
                 }
             }
         }
@@ -86,49 +92,41 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
                 throw new ArgumentNullException(nameof(obj));
             }
 
-            List<string>? temp = null;
-            foreach (IConfigurationSection section in configuration.GetChildren())
+            ValidateConfigurationKeys(typeof(Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions);
+
+            obj.MyString = configuration["MyString"]!;
+
+            if (configuration["MyInt"] is string value2)
             {
-                switch (section.Key)
-                {
-                    case "MyString":
-                        {
-                            obj.MyString = configuration["MyString"]!;
-                        }
-                        break;
-                    case "MyInt":
-                        {
-                            if (configuration["MyInt"] is string stringValue3)
-                            {
-                                obj.MyInt = ParseInt(stringValue3, () => section.Path)!;
-                            }
-                        }
-                        break;
-                    case "MyList":
-                        {
-                            if (HasChildren(section))
-                            {
-                                List<int> temp4 = obj.MyList;
-                                temp4 ??= new List<int>();
-                                BindCore(section, ref temp4, binderOptions);
-                                obj.MyList = temp4;
-                            }
-                        }
-                        break;
-                    default:
-                        {
-                            if (binderOptions?.ErrorOnUnknownConfiguration == true)
-                            {
-                                (temp ??= new List<string>()).Add($"'{section.Key}'");
-                            }
-                        }
-                        break;
-                }
+                obj.MyInt = ParseInt(value2, () => configuration.GetSection("MyInt").Path);
             }
 
-            if (temp is not null)
+            if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section3)
             {
-                throw new InvalidOperationException($"'ErrorOnUnknownConfiguration' was set on the provided BinderOptions, but the following properties were not found on the instance of {typeof(Program.MyClass)}: {string.Join(", ", temp)}");
+                List<int> temp5 = obj.MyList;
+                temp5 ??= new List<int>();
+                BindCore(section3, ref temp5, binderOptions);
+                obj.MyList = temp5;
+            }
+        }
+
+        /// <summary>If required by the binder options, validates that there are no unknown keys in the input configuration object.</summary>
+        public static void ValidateConfigurationKeys(Type type, Lazy<HashSet<string>> keys, IConfiguration configuration, BinderOptions? binderOptions)
+        {
+            if (binderOptions?.ErrorOnUnknownConfiguration is true)
+            {
+                List<string>? temp = null;
+                foreach (IConfigurationSection section in configuration.GetChildren())
+                {
+                    if (!keys.Value.Contains(section.Key))
+                    {
+                        (temp ??= new List<string>()).Add($"'{section.Key}'");
+                    }
+                }
+                if (temp is not null)
+                {
+                    throw new InvalidOperationException($"'ErrorOnUnknownConfiguration' was set on the provided BinderOptions, but the following properties were not found on the instance of {type}: {string.Join(", ", temp)}");
+                }
             }
         }
 
@@ -138,16 +136,16 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
             {
                 return true;
             }
-            return HasChildren(configuration);
+            return AsConfigWithChildren(configuration) is not null;
         }
 
-        public static bool HasChildren(IConfiguration configuration)
+        public static IConfiguration? AsConfigWithChildren(IConfiguration configuration)
         {
-            foreach (IConfigurationSection section in configuration.GetChildren())
+            foreach (IConfigurationSection _ in configuration.GetChildren())
             {
-                return true;
+                return configuration;
             }
-            return false;
+            return null;
         }
 
         public static BinderOptions? GetBinderOptions(Action<BinderOptions>? configureOptions)
@@ -165,11 +163,11 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
             return binderOptions;
         }
 
-        public static int ParseInt(string stringValue, Func<string?> getPath)
+        public static int ParseInt(string value, Func<string?> getPath)
         {
             try
             {
-                return int.Parse(stringValue, NumberStyles.Integer, CultureInfo.InvariantCulture);
+                return int.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture);
             }
             catch (Exception exception)
             {
index 56eb29d..a496d89 100644 (file)
@@ -1,7 +1,9 @@
 // <auto-generated/>
 #nullable enable
+#pragma warning disable CS0612, CS0618 // Suppress warnings about [Obsolete] member usage in generated code.
 
 /// <summary>Generated helper providing an AOT and linking compatible implementation for configuration binding.</summary>
+[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")]
 internal static class GeneratedOptionsBuilderBinder
 {
     /// <summary>Registers a configuration instance which <typeparamref name="TOptions"/> will bind against.</summary>
@@ -10,7 +12,6 @@ internal static class GeneratedOptionsBuilderBinder
         return global::GeneratedOptionsBuilderBinder.Bind(optionsBuilder, configuration, configureOptions: null);
     }
 
-
     /// <summary>Registers a configuration instance which <typeparamref name="TOptions"/> will bind against.</summary>
     public static global::Microsoft.Extensions.Options.OptionsBuilder<TOptions> Bind<TOptions>(this global::Microsoft.Extensions.Options.OptionsBuilder<TOptions> optionsBuilder, global::Microsoft.Extensions.Configuration.IConfiguration configuration, global::System.Action<global::Microsoft.Extensions.Configuration.BinderOptions>? configureOptions) where TOptions : class
     {
@@ -25,6 +26,7 @@ internal static class GeneratedOptionsBuilderBinder
 }
 
 /// <summary>Generated helper providing an AOT and linking compatible implementation for configuration binding.</summary>
+[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")]
 internal static class GeneratedServiceCollectionBinder
 {
     /// <summary>Registers a configuration instance which TOptions will bind against.</summary>
@@ -51,12 +53,16 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
     using Microsoft.Extensions.Configuration;
     using Microsoft.Extensions.DependencyInjection;
     using System;
+    using System.CodeDom.Compiler;
     using System.Collections.Generic;
     using System.Globalization;
 
     /// <summary>Provide core binding logic.</summary>
-    internal static class CoreBindingHelper
+    [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")]
+    file static class CoreBindingHelper
     {
+        private readonly static Lazy<HashSet<string>> s_configKeys_ProgramMyClass = new(() => new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "MyString", "MyInt", "MyList" });
+
         public static void BindCoreUntyped(this IConfiguration configuration, object obj, Type type, Action<BinderOptions>? configureOptions)
         {
             if (configuration is null)
@@ -90,9 +96,9 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
 
             foreach (IConfigurationSection section in configuration.GetChildren())
             {
-                if (section.Value is string stringValue1)
+                if (section.Value is string value)
                 {
-                    obj.Add(ParseInt(stringValue1, () => section.Path)!);
+                    obj.Add(ParseInt(value, () => section.Path));
                 }
             }
         }
@@ -104,49 +110,41 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
                 throw new ArgumentNullException(nameof(obj));
             }
 
-            List<string>? temp = null;
-            foreach (IConfigurationSection section in configuration.GetChildren())
+            ValidateConfigurationKeys(typeof(Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions);
+
+            obj.MyString = configuration["MyString"]!;
+
+            if (configuration["MyInt"] is string value2)
             {
-                switch (section.Key)
-                {
-                    case "MyString":
-                        {
-                            obj.MyString = configuration["MyString"]!;
-                        }
-                        break;
-                    case "MyInt":
-                        {
-                            if (configuration["MyInt"] is string stringValue3)
-                            {
-                                obj.MyInt = ParseInt(stringValue3, () => section.Path)!;
-                            }
-                        }
-                        break;
-                    case "MyList":
-                        {
-                            if (HasChildren(section))
-                            {
-                                List<int> temp4 = obj.MyList;
-                                temp4 ??= new List<int>();
-                                BindCore(section, ref temp4, binderOptions);
-                                obj.MyList = temp4;
-                            }
-                        }
-                        break;
-                    default:
-                        {
-                            if (binderOptions?.ErrorOnUnknownConfiguration == true)
-                            {
-                                (temp ??= new List<string>()).Add($"'{section.Key}'");
-                            }
-                        }
-                        break;
-                }
+                obj.MyInt = ParseInt(value2, () => configuration.GetSection("MyInt").Path);
+            }
+
+            if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section3)
+            {
+                List<int> temp5 = obj.MyList;
+                temp5 ??= new List<int>();
+                BindCore(section3, ref temp5, binderOptions);
+                obj.MyList = temp5;
             }
+        }
 
-            if (temp is not null)
+        /// <summary>If required by the binder options, validates that there are no unknown keys in the input configuration object.</summary>
+        public static void ValidateConfigurationKeys(Type type, Lazy<HashSet<string>> keys, IConfiguration configuration, BinderOptions? binderOptions)
+        {
+            if (binderOptions?.ErrorOnUnknownConfiguration is true)
             {
-                throw new InvalidOperationException($"'ErrorOnUnknownConfiguration' was set on the provided BinderOptions, but the following properties were not found on the instance of {typeof(Program.MyClass)}: {string.Join(", ", temp)}");
+                List<string>? temp = null;
+                foreach (IConfigurationSection section in configuration.GetChildren())
+                {
+                    if (!keys.Value.Contains(section.Key))
+                    {
+                        (temp ??= new List<string>()).Add($"'{section.Key}'");
+                    }
+                }
+                if (temp is not null)
+                {
+                    throw new InvalidOperationException($"'ErrorOnUnknownConfiguration' was set on the provided BinderOptions, but the following properties were not found on the instance of {type}: {string.Join(", ", temp)}");
+                }
             }
         }
 
@@ -156,16 +154,16 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
             {
                 return true;
             }
-            return HasChildren(configuration);
+            return AsConfigWithChildren(configuration) is not null;
         }
 
-        public static bool HasChildren(IConfiguration configuration)
+        public static IConfiguration? AsConfigWithChildren(IConfiguration configuration)
         {
-            foreach (IConfigurationSection section in configuration.GetChildren())
+            foreach (IConfigurationSection _ in configuration.GetChildren())
             {
-                return true;
+                return configuration;
             }
-            return false;
+            return null;
         }
 
         public static BinderOptions? GetBinderOptions(Action<BinderOptions>? configureOptions)
@@ -183,11 +181,11 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
             return binderOptions;
         }
 
-        public static int ParseInt(string stringValue, Func<string?> getPath)
+        public static int ParseInt(string value, Func<string?> getPath)
         {
             try
             {
-                return int.Parse(stringValue, NumberStyles.Integer, CultureInfo.InvariantCulture);
+                return int.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture);
             }
             catch (Exception exception)
             {
index dd47a06..23a3f50 100644 (file)
@@ -1,7 +1,9 @@
 // <auto-generated/>
 #nullable enable
+#pragma warning disable CS0612, CS0618 // Suppress warnings about [Obsolete] member usage in generated code.
 
 /// <summary>Generated helper providing an AOT and linking compatible implementation for configuration binding.</summary>
+[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")]
 internal static class GeneratedOptionsBuilderBinder
 {
     /// <summary>Registers a configuration instance which <typeparamref name="TOptions"/> will bind against.</summary>
@@ -18,6 +20,7 @@ internal static class GeneratedOptionsBuilderBinder
 }
 
 /// <summary>Generated helper providing an AOT and linking compatible implementation for configuration binding.</summary>
+[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")]
 internal static class GeneratedServiceCollectionBinder
 {
     /// <summary>Registers a configuration instance which TOptions will bind against.</summary>
@@ -44,12 +47,16 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
     using Microsoft.Extensions.Configuration;
     using Microsoft.Extensions.DependencyInjection;
     using System;
+    using System.CodeDom.Compiler;
     using System.Collections.Generic;
     using System.Globalization;
 
     /// <summary>Provide core binding logic.</summary>
-    internal static class CoreBindingHelper
+    [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")]
+    file static class CoreBindingHelper
     {
+        private readonly static Lazy<HashSet<string>> s_configKeys_ProgramMyClass = new(() => new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "MyString", "MyInt", "MyList" });
+
         public static void BindCoreUntyped(this IConfiguration configuration, object obj, Type type, Action<BinderOptions>? configureOptions)
         {
             if (configuration is null)
@@ -83,9 +90,9 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
 
             foreach (IConfigurationSection section in configuration.GetChildren())
             {
-                if (section.Value is string stringValue1)
+                if (section.Value is string value)
                 {
-                    obj.Add(ParseInt(stringValue1, () => section.Path)!);
+                    obj.Add(ParseInt(value, () => section.Path));
                 }
             }
         }
@@ -97,49 +104,41 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
                 throw new ArgumentNullException(nameof(obj));
             }
 
-            List<string>? temp = null;
-            foreach (IConfigurationSection section in configuration.GetChildren())
+            ValidateConfigurationKeys(typeof(Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions);
+
+            obj.MyString = configuration["MyString"]!;
+
+            if (configuration["MyInt"] is string value2)
             {
-                switch (section.Key)
-                {
-                    case "MyString":
-                        {
-                            obj.MyString = configuration["MyString"]!;
-                        }
-                        break;
-                    case "MyInt":
-                        {
-                            if (configuration["MyInt"] is string stringValue3)
-                            {
-                                obj.MyInt = ParseInt(stringValue3, () => section.Path)!;
-                            }
-                        }
-                        break;
-                    case "MyList":
-                        {
-                            if (HasChildren(section))
-                            {
-                                List<int> temp4 = obj.MyList;
-                                temp4 ??= new List<int>();
-                                BindCore(section, ref temp4, binderOptions);
-                                obj.MyList = temp4;
-                            }
-                        }
-                        break;
-                    default:
-                        {
-                            if (binderOptions?.ErrorOnUnknownConfiguration == true)
-                            {
-                                (temp ??= new List<string>()).Add($"'{section.Key}'");
-                            }
-                        }
-                        break;
-                }
+                obj.MyInt = ParseInt(value2, () => configuration.GetSection("MyInt").Path);
             }
 
-            if (temp is not null)
+            if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section3)
             {
-                throw new InvalidOperationException($"'ErrorOnUnknownConfiguration' was set on the provided BinderOptions, but the following properties were not found on the instance of {typeof(Program.MyClass)}: {string.Join(", ", temp)}");
+                List<int> temp5 = obj.MyList;
+                temp5 ??= new List<int>();
+                BindCore(section3, ref temp5, binderOptions);
+                obj.MyList = temp5;
+            }
+        }
+
+        /// <summary>If required by the binder options, validates that there are no unknown keys in the input configuration object.</summary>
+        public static void ValidateConfigurationKeys(Type type, Lazy<HashSet<string>> keys, IConfiguration configuration, BinderOptions? binderOptions)
+        {
+            if (binderOptions?.ErrorOnUnknownConfiguration is true)
+            {
+                List<string>? temp = null;
+                foreach (IConfigurationSection section in configuration.GetChildren())
+                {
+                    if (!keys.Value.Contains(section.Key))
+                    {
+                        (temp ??= new List<string>()).Add($"'{section.Key}'");
+                    }
+                }
+                if (temp is not null)
+                {
+                    throw new InvalidOperationException($"'ErrorOnUnknownConfiguration' was set on the provided BinderOptions, but the following properties were not found on the instance of {type}: {string.Join(", ", temp)}");
+                }
             }
         }
 
@@ -149,16 +148,16 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
             {
                 return true;
             }
-            return HasChildren(configuration);
+            return AsConfigWithChildren(configuration) is not null;
         }
 
-        public static bool HasChildren(IConfiguration configuration)
+        public static IConfiguration? AsConfigWithChildren(IConfiguration configuration)
         {
-            foreach (IConfigurationSection section in configuration.GetChildren())
+            foreach (IConfigurationSection _ in configuration.GetChildren())
             {
-                return true;
+                return configuration;
             }
-            return false;
+            return null;
         }
 
         public static BinderOptions? GetBinderOptions(Action<BinderOptions>? configureOptions)
@@ -176,11 +175,11 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
             return binderOptions;
         }
 
-        public static int ParseInt(string stringValue, Func<string?> getPath)
+        public static int ParseInt(string value, Func<string?> getPath)
         {
             try
             {
-                return int.Parse(stringValue, NumberStyles.Integer, CultureInfo.InvariantCulture);
+                return int.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture);
             }
             catch (Exception exception)
             {
index 4d8f1bf..c38f1fd 100644 (file)
@@ -1,7 +1,9 @@
 // <auto-generated/>
 #nullable enable
+#pragma warning disable CS0612, CS0618 // Suppress warnings about [Obsolete] member usage in generated code.
 
 /// <summary>Generated helper providing an AOT and linking compatible implementation for configuration binding.</summary>
+[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")]
 internal static class GeneratedConfigurationBinder
 {
     /// <summary>Attempts to bind the given object instance to configuration values by matching property names against configuration keys recursively.</summary>
@@ -12,12 +14,16 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
 {
     using Microsoft.Extensions.Configuration;
     using System;
+    using System.CodeDom.Compiler;
     using System.Collections.Generic;
     using System.Globalization;
 
     /// <summary>Provide core binding logic.</summary>
-    internal static class CoreBindingHelper
+    [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")]
+    file static class CoreBindingHelper
     {
+        private readonly static Lazy<HashSet<string>> s_configKeys_ProgramMyClass = new(() => new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "Prop0", "Prop1", "Prop2", "Prop3", "Prop4", "Prop5", "Prop6", "Prop8", "Prop9", "Prop10", "Prop13", "Prop14", "Prop15", "Prop16", "Prop17", "Prop19", "Prop20", "Prop21", "Prop23", "Prop24", "Prop25", "Prop26", "Prop27", "Prop7", "Prop11", "Prop12", "Prop18", "Prop22" });
+
         public static void BindCore(IConfiguration configuration, ref Program.MyClass obj, BinderOptions? binderOptions)
         {
             if (obj is null)
@@ -25,251 +31,168 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
                 throw new ArgumentNullException(nameof(obj));
             }
 
-            List<string>? temp = null;
-            foreach (IConfigurationSection section in configuration.GetChildren())
+            ValidateConfigurationKeys(typeof(Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions);
+
+            if (configuration["Prop0"] is string value0)
             {
-                switch (section.Key)
-                {
-                    case "Prop0":
-                        {
-                            if (configuration["Prop0"] is string stringValue0)
-                            {
-                                obj.Prop0 = ParseBool(stringValue0, () => section.Path)!;
-                            }
-                        }
-                        break;
-                    case "Prop1":
-                        {
-                            if (configuration["Prop1"] is string stringValue1)
-                            {
-                                obj.Prop1 = ParseByte(stringValue1, () => section.Path)!;
-                            }
-                        }
-                        break;
-                    case "Prop2":
-                        {
-                            if (configuration["Prop2"] is string stringValue2)
-                            {
-                                obj.Prop2 = ParseSbyte(stringValue2, () => section.Path)!;
-                            }
-                        }
-                        break;
-                    case "Prop3":
-                        {
-                            if (configuration["Prop3"] is string stringValue3)
-                            {
-                                obj.Prop3 = ParseChar(stringValue3, () => section.Path)!;
-                            }
-                        }
-                        break;
-                    case "Prop4":
-                        {
-                            if (configuration["Prop4"] is string stringValue4)
-                            {
-                                obj.Prop4 = ParseDouble(stringValue4, () => section.Path)!;
-                            }
-                        }
-                        break;
-                    case "Prop5":
-                        {
-                            obj.Prop5 = configuration["Prop5"]!;
-                        }
-                        break;
-                    case "Prop6":
-                        {
-                            if (configuration["Prop6"] is string stringValue6)
-                            {
-                                obj.Prop6 = ParseInt(stringValue6, () => section.Path)!;
-                            }
-                        }
-                        break;
-                    case "Prop8":
-                        {
-                            if (configuration["Prop8"] is string stringValue7)
-                            {
-                                obj.Prop8 = ParseShort(stringValue7, () => section.Path)!;
-                            }
-                        }
-                        break;
-                    case "Prop9":
-                        {
-                            if (configuration["Prop9"] is string stringValue8)
-                            {
-                                obj.Prop9 = ParseLong(stringValue8, () => section.Path)!;
-                            }
-                        }
-                        break;
-                    case "Prop10":
-                        {
-                            if (configuration["Prop10"] is string stringValue9)
-                            {
-                                obj.Prop10 = ParseFloat(stringValue9, () => section.Path)!;
-                            }
-                        }
-                        break;
-                    case "Prop13":
-                        {
-                            if (configuration["Prop13"] is string stringValue10)
-                            {
-                                obj.Prop13 = ParseUshort(stringValue10, () => section.Path)!;
-                            }
-                        }
-                        break;
-                    case "Prop14":
-                        {
-                            if (configuration["Prop14"] is string stringValue11)
-                            {
-                                obj.Prop14 = ParseUint(stringValue11, () => section.Path)!;
-                            }
-                        }
-                        break;
-                    case "Prop15":
-                        {
-                            if (configuration["Prop15"] is string stringValue12)
-                            {
-                                obj.Prop15 = ParseUlong(stringValue12, () => section.Path)!;
-                            }
-                        }
-                        break;
-                    case "Prop16":
-                        {
-                            obj.Prop16 = configuration["Prop16"]!;
-                        }
-                        break;
-                    case "Prop17":
-                        {
-                            if (configuration["Prop17"] is string stringValue14)
-                            {
-                                obj.Prop17 = ParseCultureInfo(stringValue14, () => section.Path)!;
-                            }
-                        }
-                        break;
-                    case "Prop19":
-                        {
-                            if (configuration["Prop19"] is string stringValue15)
-                            {
-                                obj.Prop19 = ParseDateTime(stringValue15, () => section.Path)!;
-                            }
-                        }
-                        break;
-                    case "Prop20":
-                        {
-                            if (configuration["Prop20"] is string stringValue16)
-                            {
-                                obj.Prop20 = ParseDateTimeOffset(stringValue16, () => section.Path)!;
-                            }
-                        }
-                        break;
-                    case "Prop21":
-                        {
-                            if (configuration["Prop21"] is string stringValue17)
-                            {
-                                obj.Prop21 = ParseDecimal(stringValue17, () => section.Path)!;
-                            }
-                        }
-                        break;
-                    case "Prop23":
-                        {
-                            if (configuration["Prop23"] is string stringValue18)
-                            {
-                                obj.Prop23 = ParseInt(stringValue18, () => section.Path)!;
-                            }
-                        }
-                        break;
-                    case "Prop24":
-                        {
-                            if (configuration["Prop24"] is string stringValue19)
-                            {
-                                obj.Prop24 = ParseDateTime(stringValue19, () => section.Path)!;
-                            }
-                        }
-                        break;
-                    case "Prop25":
-                        {
-                            if (configuration["Prop25"] is string stringValue20)
-                            {
-                                obj.Prop25 = ParseUri(stringValue20, () => section.Path)!;
-                            }
-                        }
-                        break;
-                    case "Prop26":
-                        {
-                            if (configuration["Prop26"] is string stringValue21)
-                            {
-                                obj.Prop26 = ParseVersion(stringValue21, () => section.Path)!;
-                            }
-                        }
-                        break;
-                    case "Prop27":
-                        {
-                            if (configuration["Prop27"] is string stringValue22)
-                            {
-                                obj.Prop27 = ParseDayOfWeek(stringValue22, () => section.Path)!;
-                            }
-                        }
-                        break;
-                    case "Prop7":
-                        {
-                            if (configuration["Prop7"] is string stringValue23)
-                            {
-                                obj.Prop7 = ParseInt128(stringValue23, () => section.Path)!;
-                            }
-                        }
-                        break;
-                    case "Prop11":
-                        {
-                            if (configuration["Prop11"] is string stringValue24)
-                            {
-                                obj.Prop11 = ParseHalf(stringValue24, () => section.Path)!;
-                            }
-                        }
-                        break;
-                    case "Prop12":
-                        {
-                            if (configuration["Prop12"] is string stringValue25)
-                            {
-                                obj.Prop12 = ParseUInt128(stringValue25, () => section.Path)!;
-                            }
-                        }
-                        break;
-                    case "Prop18":
-                        {
-                            if (configuration["Prop18"] is string stringValue26)
-                            {
-                                obj.Prop18 = ParseDateOnly(stringValue26, () => section.Path)!;
-                            }
-                        }
-                        break;
-                    case "Prop22":
-                        {
-                            if (configuration["Prop22"] is string stringValue27)
-                            {
-                                obj.Prop22 = ParseByteArray(stringValue27, () => section.Path)!;
-                            }
-                        }
-                        break;
-                    default:
-                        {
-                            if (binderOptions?.ErrorOnUnknownConfiguration == true)
-                            {
-                                (temp ??= new List<string>()).Add($"'{section.Key}'");
-                            }
-                        }
-                        break;
-                }
+                obj.Prop0 = ParseBool(value0, () => configuration.GetSection("Prop0").Path);
+            }
+
+            if (configuration["Prop1"] is string value1)
+            {
+                obj.Prop1 = ParseByte(value1, () => configuration.GetSection("Prop1").Path);
+            }
+
+            if (configuration["Prop2"] is string value2)
+            {
+                obj.Prop2 = ParseSbyte(value2, () => configuration.GetSection("Prop2").Path);
+            }
+
+            if (configuration["Prop3"] is string value3)
+            {
+                obj.Prop3 = ParseChar(value3, () => configuration.GetSection("Prop3").Path);
+            }
+
+            if (configuration["Prop4"] is string value4)
+            {
+                obj.Prop4 = ParseDouble(value4, () => configuration.GetSection("Prop4").Path);
+            }
+
+            obj.Prop5 = configuration["Prop5"]!;
+
+            if (configuration["Prop6"] is string value6)
+            {
+                obj.Prop6 = ParseInt(value6, () => configuration.GetSection("Prop6").Path);
+            }
+
+            if (configuration["Prop8"] is string value7)
+            {
+                obj.Prop8 = ParseShort(value7, () => configuration.GetSection("Prop8").Path);
+            }
+
+            if (configuration["Prop9"] is string value8)
+            {
+                obj.Prop9 = ParseLong(value8, () => configuration.GetSection("Prop9").Path);
+            }
+
+            if (configuration["Prop10"] is string value9)
+            {
+                obj.Prop10 = ParseFloat(value9, () => configuration.GetSection("Prop10").Path);
+            }
+
+            if (configuration["Prop13"] is string value10)
+            {
+                obj.Prop13 = ParseUshort(value10, () => configuration.GetSection("Prop13").Path);
+            }
+
+            if (configuration["Prop14"] is string value11)
+            {
+                obj.Prop14 = ParseUint(value11, () => configuration.GetSection("Prop14").Path);
+            }
+
+            if (configuration["Prop15"] is string value12)
+            {
+                obj.Prop15 = ParseUlong(value12, () => configuration.GetSection("Prop15").Path);
             }
 
-            if (temp is not null)
+            obj.Prop16 = configuration["Prop16"]!;
+
+            if (configuration["Prop17"] is string value14)
             {
-                throw new InvalidOperationException($"'ErrorOnUnknownConfiguration' was set on the provided BinderOptions, but the following properties were not found on the instance of {typeof(Program.MyClass)}: {string.Join(", ", temp)}");
+                obj.Prop17 = ParseCultureInfo(value14, () => configuration.GetSection("Prop17").Path);
+            }
+
+            if (configuration["Prop19"] is string value15)
+            {
+                obj.Prop19 = ParseDateTime(value15, () => configuration.GetSection("Prop19").Path);
+            }
+
+            if (configuration["Prop20"] is string value16)
+            {
+                obj.Prop20 = ParseDateTimeOffset(value16, () => configuration.GetSection("Prop20").Path);
+            }
+
+            if (configuration["Prop21"] is string value17)
+            {
+                obj.Prop21 = ParseDecimal(value17, () => configuration.GetSection("Prop21").Path);
+            }
+
+            if (configuration["Prop23"] is string value18)
+            {
+                obj.Prop23 = ParseInt(value18, () => configuration.GetSection("Prop23").Path);
+            }
+
+            if (configuration["Prop24"] is string value19)
+            {
+                obj.Prop24 = ParseDateTime(value19, () => configuration.GetSection("Prop24").Path);
+            }
+
+            if (configuration["Prop25"] is string value20)
+            {
+                obj.Prop25 = ParseUri(value20, () => configuration.GetSection("Prop25").Path);
+            }
+
+            if (configuration["Prop26"] is string value21)
+            {
+                obj.Prop26 = ParseVersion(value21, () => configuration.GetSection("Prop26").Path);
+            }
+
+            if (configuration["Prop27"] is string value22)
+            {
+                obj.Prop27 = ParseDayOfWeek(value22, () => configuration.GetSection("Prop27").Path);
+            }
+
+            if (configuration["Prop7"] is string value23)
+            {
+                obj.Prop7 = ParseInt128(value23, () => configuration.GetSection("Prop7").Path);
+            }
+
+            if (configuration["Prop11"] is string value24)
+            {
+                obj.Prop11 = ParseHalf(value24, () => configuration.GetSection("Prop11").Path);
+            }
+
+            if (configuration["Prop12"] is string value25)
+            {
+                obj.Prop12 = ParseUInt128(value25, () => configuration.GetSection("Prop12").Path);
+            }
+
+            if (configuration["Prop18"] is string value26)
+            {
+                obj.Prop18 = ParseDateOnly(value26, () => configuration.GetSection("Prop18").Path);
+            }
+
+            if (configuration["Prop22"] is string value27)
+            {
+                obj.Prop22 = ParseByteArray(value27, () => configuration.GetSection("Prop22").Path);
+            }
+        }
+
+        /// <summary>If required by the binder options, validates that there are no unknown keys in the input configuration object.</summary>
+        public static void ValidateConfigurationKeys(Type type, Lazy<HashSet<string>> keys, IConfiguration configuration, BinderOptions? binderOptions)
+        {
+            if (binderOptions?.ErrorOnUnknownConfiguration is true)
+            {
+                List<string>? temp = null;
+                foreach (IConfigurationSection section in configuration.GetChildren())
+                {
+                    if (!keys.Value.Contains(section.Key))
+                    {
+                        (temp ??= new List<string>()).Add($"'{section.Key}'");
+                    }
+                }
+                if (temp is not null)
+                {
+                    throw new InvalidOperationException($"'ErrorOnUnknownConfiguration' was set on the provided BinderOptions, but the following properties were not found on the instance of {type}: {string.Join(", ", temp)}");
+                }
             }
         }
 
-        public static bool ParseBool(string stringValue, Func<string?> getPath)
+        public static bool ParseBool(string value, Func<string?> getPath)
         {
             try
             {
-                return bool.Parse(stringValue);
+                return bool.Parse(value);
             }
             catch (Exception exception)
             {
@@ -277,11 +200,11 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
             }
         }
 
-        public static byte ParseByte(string stringValue, Func<string?> getPath)
+        public static byte ParseByte(string value, Func<string?> getPath)
         {
             try
             {
-                return byte.Parse(stringValue, NumberStyles.Integer, CultureInfo.InvariantCulture);
+                return byte.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture);
             }
             catch (Exception exception)
             {
@@ -289,11 +212,11 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
             }
         }
 
-        public static sbyte ParseSbyte(string stringValue, Func<string?> getPath)
+        public static sbyte ParseSbyte(string value, Func<string?> getPath)
         {
             try
             {
-                return sbyte.Parse(stringValue, NumberStyles.Integer, CultureInfo.InvariantCulture);
+                return sbyte.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture);
             }
             catch (Exception exception)
             {
@@ -301,11 +224,11 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
             }
         }
 
-        public static char ParseChar(string stringValue, Func<string?> getPath)
+        public static char ParseChar(string value, Func<string?> getPath)
         {
             try
             {
-                return char.Parse(stringValue);
+                return char.Parse(value);
             }
             catch (Exception exception)
             {
@@ -313,11 +236,11 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
             }
         }
 
-        public static double ParseDouble(string stringValue, Func<string?> getPath)
+        public static double ParseDouble(string value, Func<string?> getPath)
         {
             try
             {
-                return double.Parse(stringValue, NumberStyles.Float, CultureInfo.InvariantCulture);
+                return double.Parse(value, NumberStyles.Float, CultureInfo.InvariantCulture);
             }
             catch (Exception exception)
             {
@@ -325,11 +248,11 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
             }
         }
 
-        public static int ParseInt(string stringValue, Func<string?> getPath)
+        public static int ParseInt(string value, Func<string?> getPath)
         {
             try
             {
-                return int.Parse(stringValue, NumberStyles.Integer, CultureInfo.InvariantCulture);
+                return int.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture);
             }
             catch (Exception exception)
             {
@@ -337,11 +260,11 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
             }
         }
 
-        public static short ParseShort(string stringValue, Func<string?> getPath)
+        public static short ParseShort(string value, Func<string?> getPath)
         {
             try
             {
-                return short.Parse(stringValue, NumberStyles.Integer, CultureInfo.InvariantCulture);
+                return short.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture);
             }
             catch (Exception exception)
             {
@@ -349,11 +272,11 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
             }
         }
 
-        public static long ParseLong(string stringValue, Func<string?> getPath)
+        public static long ParseLong(string value, Func<string?> getPath)
         {
             try
             {
-                return long.Parse(stringValue, NumberStyles.Integer, CultureInfo.InvariantCulture);
+                return long.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture);
             }
             catch (Exception exception)
             {
@@ -361,11 +284,11 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
             }
         }
 
-        public static float ParseFloat(string stringValue, Func<string?> getPath)
+        public static float ParseFloat(string value, Func<string?> getPath)
         {
             try
             {
-                return float.Parse(stringValue, NumberStyles.Float, CultureInfo.InvariantCulture);
+                return float.Parse(value, NumberStyles.Float, CultureInfo.InvariantCulture);
             }
             catch (Exception exception)
             {
@@ -373,11 +296,11 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
             }
         }
 
-        public static ushort ParseUshort(string stringValue, Func<string?> getPath)
+        public static ushort ParseUshort(string value, Func<string?> getPath)
         {
             try
             {
-                return ushort.Parse(stringValue, NumberStyles.Integer, CultureInfo.InvariantCulture);
+                return ushort.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture);
             }
             catch (Exception exception)
             {
@@ -385,11 +308,11 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
             }
         }
 
-        public static uint ParseUint(string stringValue, Func<string?> getPath)
+        public static uint ParseUint(string value, Func<string?> getPath)
         {
             try
             {
-                return uint.Parse(stringValue, NumberStyles.Integer, CultureInfo.InvariantCulture);
+                return uint.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture);
             }
             catch (Exception exception)
             {
@@ -397,11 +320,11 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
             }
         }
 
-        public static ulong ParseUlong(string stringValue, Func<string?> getPath)
+        public static ulong ParseUlong(string value, Func<string?> getPath)
         {
             try
             {
-                return ulong.Parse(stringValue, NumberStyles.Integer, CultureInfo.InvariantCulture);
+                return ulong.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture);
             }
             catch (Exception exception)
             {
@@ -409,11 +332,11 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
             }
         }
 
-        public static CultureInfo ParseCultureInfo(string stringValue, Func<string?> getPath)
+        public static CultureInfo ParseCultureInfo(string value, Func<string?> getPath)
         {
             try
             {
-                return CultureInfo.GetCultureInfo(stringValue);
+                return CultureInfo.GetCultureInfo(value);
             }
             catch (Exception exception)
             {
@@ -421,11 +344,11 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
             }
         }
 
-        public static DateTime ParseDateTime(string stringValue, Func<string?> getPath)
+        public static DateTime ParseDateTime(string value, Func<string?> getPath)
         {
             try
             {
-                return DateTime.Parse(stringValue, CultureInfo.InvariantCulture);
+                return DateTime.Parse(value, CultureInfo.InvariantCulture);
             }
             catch (Exception exception)
             {
@@ -433,11 +356,11 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
             }
         }
 
-        public static DateTimeOffset ParseDateTimeOffset(string stringValue, Func<string?> getPath)
+        public static DateTimeOffset ParseDateTimeOffset(string value, Func<string?> getPath)
         {
             try
             {
-                return DateTimeOffset.Parse(stringValue, CultureInfo.InvariantCulture);
+                return DateTimeOffset.Parse(value, CultureInfo.InvariantCulture);
             }
             catch (Exception exception)
             {
@@ -445,11 +368,11 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
             }
         }
 
-        public static decimal ParseDecimal(string stringValue, Func<string?> getPath)
+        public static decimal ParseDecimal(string value, Func<string?> getPath)
         {
             try
             {
-                return decimal.Parse(stringValue, NumberStyles.Float, CultureInfo.InvariantCulture);
+                return decimal.Parse(value, NumberStyles.Float, CultureInfo.InvariantCulture);
             }
             catch (Exception exception)
             {
@@ -457,11 +380,11 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
             }
         }
 
-        public static TimeSpan ParseTimeSpan(string stringValue, Func<string?> getPath)
+        public static TimeSpan ParseTimeSpan(string value, Func<string?> getPath)
         {
             try
             {
-                return TimeSpan.Parse(stringValue, CultureInfo.InvariantCulture);
+                return TimeSpan.Parse(value, CultureInfo.InvariantCulture);
             }
             catch (Exception exception)
             {
@@ -469,11 +392,11 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
             }
         }
 
-        public static Guid ParseGuid(string stringValue, Func<string?> getPath)
+        public static Guid ParseGuid(string value, Func<string?> getPath)
         {
             try
             {
-                return Guid.Parse(stringValue);
+                return Guid.Parse(value);
             }
             catch (Exception exception)
             {
@@ -481,11 +404,11 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
             }
         }
 
-        public static Uri ParseUri(string stringValue, Func<string?> getPath)
+        public static Uri ParseUri(string value, Func<string?> getPath)
         {
             try
             {
-                return new Uri(stringValue, UriKind.RelativeOrAbsolute);
+                return new Uri(value, UriKind.RelativeOrAbsolute);
             }
             catch (Exception exception)
             {
@@ -493,11 +416,11 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
             }
         }
 
-        public static Version ParseVersion(string stringValue, Func<string?> getPath)
+        public static Version ParseVersion(string value, Func<string?> getPath)
         {
             try
             {
-                return Version.Parse(stringValue);
+                return Version.Parse(value);
             }
             catch (Exception exception)
             {
@@ -505,11 +428,11 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
             }
         }
 
-        public static DayOfWeek ParseDayOfWeek(string stringValue, Func<string?> getPath)
+        public static DayOfWeek ParseDayOfWeek(string value, Func<string?> getPath)
         {
             try
             {
-                return (DayOfWeek)Enum.Parse(typeof(DayOfWeek), stringValue, ignoreCase: true);
+                return (DayOfWeek)Enum.Parse(typeof(DayOfWeek), value, ignoreCase: true);
             }
             catch (Exception exception)
             {
@@ -517,11 +440,11 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
             }
         }
 
-        public static Int128 ParseInt128(string stringValue, Func<string?> getPath)
+        public static Int128 ParseInt128(string value, Func<string?> getPath)
         {
             try
             {
-                return Int128.Parse(stringValue, CultureInfo.InvariantCulture);
+                return Int128.Parse(value, CultureInfo.InvariantCulture);
             }
             catch (Exception exception)
             {
@@ -529,11 +452,11 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
             }
         }
 
-        public static Half ParseHalf(string stringValue, Func<string?> getPath)
+        public static Half ParseHalf(string value, Func<string?> getPath)
         {
             try
             {
-                return Half.Parse(stringValue, CultureInfo.InvariantCulture);
+                return Half.Parse(value, CultureInfo.InvariantCulture);
             }
             catch (Exception exception)
             {
@@ -541,11 +464,11 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
             }
         }
 
-        public static UInt128 ParseUInt128(string stringValue, Func<string?> getPath)
+        public static UInt128 ParseUInt128(string value, Func<string?> getPath)
         {
             try
             {
-                return UInt128.Parse(stringValue, CultureInfo.InvariantCulture);
+                return UInt128.Parse(value, CultureInfo.InvariantCulture);
             }
             catch (Exception exception)
             {
@@ -553,11 +476,11 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
             }
         }
 
-        public static DateOnly ParseDateOnly(string stringValue, Func<string?> getPath)
+        public static DateOnly ParseDateOnly(string value, Func<string?> getPath)
         {
             try
             {
-                return DateOnly.Parse(stringValue, CultureInfo.InvariantCulture);
+                return DateOnly.Parse(value, CultureInfo.InvariantCulture);
             }
             catch (Exception exception)
             {
@@ -565,11 +488,11 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
             }
         }
 
-        public static TimeOnly ParseTimeOnly(string stringValue, Func<string?> getPath)
+        public static TimeOnly ParseTimeOnly(string value, Func<string?> getPath)
         {
             try
             {
-                return TimeOnly.Parse(stringValue, CultureInfo.InvariantCulture);
+                return TimeOnly.Parse(value, CultureInfo.InvariantCulture);
             }
             catch (Exception exception)
             {
@@ -577,11 +500,11 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
             }
         }
 
-        public static byte[] ParseByteArray(string stringValue, Func<string?> getPath)
+        public static byte[] ParseByteArray(string value, Func<string?> getPath)
         {
             try
             {
-                return Convert.FromBase64String(stringValue);
+                return Convert.FromBase64String(value);
             }
             catch (Exception exception)
             {
index 52cb0f6..c5717e3 100644 (file)
@@ -1,7 +1,9 @@
 // <auto-generated/>
 #nullable enable
+#pragma warning disable CS0612, CS0618 // Suppress warnings about [Obsolete] member usage in generated code.
 
 /// <summary>Generated helper providing an AOT and linking compatible implementation for configuration binding.</summary>
+[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")]
 internal static class GeneratedServiceCollectionBinder
 {
     /// <summary>Registers a configuration instance which TOptions will bind against.</summary>
@@ -33,12 +35,17 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
 {
     using Microsoft.Extensions.Configuration;
     using System;
+    using System.CodeDom.Compiler;
     using System.Collections.Generic;
     using System.Globalization;
 
     /// <summary>Provide core binding logic.</summary>
-    internal static class CoreBindingHelper
+    [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")]
+    file static class CoreBindingHelper
     {
+        private readonly static Lazy<HashSet<string>> s_configKeys_ProgramMyClass2 = new(() => new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "MyInt" });
+        private readonly static Lazy<HashSet<string>> s_configKeys_ProgramMyClass = new(() => new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "MyString", "MyInt", "MyList", "MyList2", "MyDictionary" });
+
         public static void BindCoreUntyped(this IConfiguration configuration, object obj, Type type, Action<BinderOptions>? configureOptions)
         {
             if (configuration is null)
@@ -72,9 +79,9 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
 
             foreach (IConfigurationSection section in configuration.GetChildren())
             {
-                if (section.Value is string stringValue1)
+                if (section.Value is string value)
                 {
-                    obj.Add(ParseInt(stringValue1, () => section.Path)!);
+                    obj.Add(ParseInt(value, () => section.Path));
                 }
             }
         }
@@ -86,33 +93,11 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
                 throw new ArgumentNullException(nameof(obj));
             }
 
-            List<string>? temp = null;
-            foreach (IConfigurationSection section in configuration.GetChildren())
-            {
-                switch (section.Key)
-                {
-                    case "MyInt":
-                        {
-                            if (configuration["MyInt"] is string stringValue2)
-                            {
-                                obj.MyInt = ParseInt(stringValue2, () => section.Path)!;
-                            }
-                        }
-                        break;
-                    default:
-                        {
-                            if (binderOptions?.ErrorOnUnknownConfiguration == true)
-                            {
-                                (temp ??= new List<string>()).Add($"'{section.Key}'");
-                            }
-                        }
-                        break;
-                }
-            }
+            ValidateConfigurationKeys(typeof(Program.MyClass2), s_configKeys_ProgramMyClass2, configuration, binderOptions);
 
-            if (temp is not null)
+            if (configuration["MyInt"] is string value1)
             {
-                throw new InvalidOperationException($"'ErrorOnUnknownConfiguration' was set on the provided BinderOptions, but the following properties were not found on the instance of {typeof(Program.MyClass2)}: {string.Join(", ", temp)}");
+                obj.MyInt = ParseInt(value1, () => configuration.GetSection("MyInt").Path);
             }
         }
 
@@ -125,9 +110,9 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
 
             foreach (IConfigurationSection section in configuration.GetChildren())
             {
-                var element = new Program.MyClass2();
-                BindCore(section, ref element, binderOptions);
-                obj.Add(element);
+                var value = new Program.MyClass2();
+                BindCore(section, ref value, binderOptions);
+                obj.Add(value);
             }
         }
 
@@ -140,9 +125,9 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
 
             foreach (IConfigurationSection section in configuration.GetChildren())
             {
-                if (section.Value is string stringValue5)
+                if (section.Value is string value)
                 {
-                    obj[section.Key!] = stringValue5!;
+                    obj[section.Key] = value;
                 }
             }
         }
@@ -154,71 +139,57 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
                 throw new ArgumentNullException(nameof(obj));
             }
 
-            List<string>? temp = null;
-            foreach (IConfigurationSection section in configuration.GetChildren())
+            ValidateConfigurationKeys(typeof(Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions);
+
+            obj.MyString = configuration["MyString"]!;
+
+            if (configuration["MyInt"] is string value4)
             {
-                switch (section.Key)
-                {
-                    case "MyString":
-                        {
-                            obj.MyString = configuration["MyString"]!;
-                        }
-                        break;
-                    case "MyInt":
-                        {
-                            if (configuration["MyInt"] is string stringValue7)
-                            {
-                                obj.MyInt = ParseInt(stringValue7, () => section.Path)!;
-                            }
-                        }
-                        break;
-                    case "MyList":
-                        {
-                            if (HasChildren(section))
-                            {
-                                List<int> temp8 = obj.MyList;
-                                temp8 ??= new List<int>();
-                                BindCore(section, ref temp8, binderOptions);
-                                obj.MyList = temp8;
-                            }
-                        }
-                        break;
-                    case "MyList2":
-                        {
-                            if (HasChildren(section))
-                            {
-                                List<Program.MyClass2> temp9 = obj.MyList2;
-                                temp9 ??= new List<Program.MyClass2>();
-                                BindCore(section, ref temp9, binderOptions);
-                                obj.MyList2 = temp9;
-                            }
-                        }
-                        break;
-                    case "MyDictionary":
-                        {
-                            if (HasChildren(section))
-                            {
-                                Dictionary<string, string> temp10 = obj.MyDictionary;
-                                temp10 ??= new Dictionary<string, string>();
-                                BindCore(section, ref temp10, binderOptions);
-                                obj.MyDictionary = temp10;
-                            }
-                        }
-                        break;
-                    default:
-                        {
-                            if (binderOptions?.ErrorOnUnknownConfiguration == true)
-                            {
-                                (temp ??= new List<string>()).Add($"'{section.Key}'");
-                            }
-                        }
-                        break;
-                }
+                obj.MyInt = ParseInt(value4, () => configuration.GetSection("MyInt").Path);
+            }
+
+            if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section5)
+            {
+                List<int> temp7 = obj.MyList;
+                temp7 ??= new List<int>();
+                BindCore(section5, ref temp7, binderOptions);
+                obj.MyList = temp7;
             }
 
-            if (temp is not null)
+            if (AsConfigWithChildren(configuration.GetSection("MyList2")) is IConfigurationSection section8)
             {
-                throw new InvalidOperationException($"'ErrorOnUnknownConfiguration' was set on the provided BinderOptions, but the following properties were not found on the instance of {typeof(Program.MyClass)}: {string.Join(", ", temp)}");
+                List<Program.MyClass2> temp10 = obj.MyList2;
+                temp10 ??= new List<Program.MyClass2>();
+                BindCore(section8, ref temp10, binderOptions);
+                obj.MyList2 = temp10;
+            }
+
+            if (AsConfigWithChildren(configuration.GetSection("MyDictionary")) is IConfigurationSection section11)
+            {
+                Dictionary<string, string> temp13 = obj.MyDictionary;
+                temp13 ??= new Dictionary<string, string>();
+                BindCore(section11, ref temp13, binderOptions);
+                obj.MyDictionary = temp13;
+            }
+        }
+
+        /// <summary>If required by the binder options, validates that there are no unknown keys in the input configuration object.</summary>
+        public static void ValidateConfigurationKeys(Type type, Lazy<HashSet<string>> keys, IConfiguration configuration, BinderOptions? binderOptions)
+        {
+            if (binderOptions?.ErrorOnUnknownConfiguration is true)
+            {
+                List<string>? temp = null;
+                foreach (IConfigurationSection section in configuration.GetChildren())
+                {
+                    if (!keys.Value.Contains(section.Key))
+                    {
+                        (temp ??= new List<string>()).Add($"'{section.Key}'");
+                    }
+                }
+                if (temp is not null)
+                {
+                    throw new InvalidOperationException($"'ErrorOnUnknownConfiguration' was set on the provided BinderOptions, but the following properties were not found on the instance of {type}: {string.Join(", ", temp)}");
+                }
             }
         }
 
@@ -228,16 +199,16 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
             {
                 return true;
             }
-            return HasChildren(configuration);
+            return AsConfigWithChildren(configuration) is not null;
         }
 
-        public static bool HasChildren(IConfiguration configuration)
+        public static IConfiguration? AsConfigWithChildren(IConfiguration configuration)
         {
-            foreach (IConfigurationSection section in configuration.GetChildren())
+            foreach (IConfigurationSection _ in configuration.GetChildren())
             {
-                return true;
+                return configuration;
             }
-            return false;
+            return null;
         }
 
         public static BinderOptions? GetBinderOptions(Action<BinderOptions>? configureOptions)
@@ -255,11 +226,11 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
             return binderOptions;
         }
 
-        public static int ParseInt(string stringValue, Func<string?> getPath)
+        public static int ParseInt(string value, Func<string?> getPath)
         {
             try
             {
-                return int.Parse(stringValue, NumberStyles.Integer, CultureInfo.InvariantCulture);
+                return int.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture);
             }
             catch (Exception exception)
             {
index 9c7bc2a..1b1405c 100644 (file)
@@ -1,7 +1,9 @@
 // <auto-generated/>
 #nullable enable
+#pragma warning disable CS0612, CS0618 // Suppress warnings about [Obsolete] member usage in generated code.
 
 /// <summary>Generated helper providing an AOT and linking compatible implementation for configuration binding.</summary>
+[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")]
 internal static class GeneratedServiceCollectionBinder
 {
     /// <summary>Registers a configuration instance which TOptions will bind against.</summary>
@@ -33,12 +35,17 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
 {
     using Microsoft.Extensions.Configuration;
     using System;
+    using System.CodeDom.Compiler;
     using System.Collections.Generic;
     using System.Globalization;
 
     /// <summary>Provide core binding logic.</summary>
-    internal static class CoreBindingHelper
+    [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")]
+    file static class CoreBindingHelper
     {
+        private readonly static Lazy<HashSet<string>> s_configKeys_ProgramMyClass2 = new(() => new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "MyInt" });
+        private readonly static Lazy<HashSet<string>> s_configKeys_ProgramMyClass = new(() => new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "MyString", "MyInt", "MyList", "MyList2", "MyDictionary" });
+
         public static void BindCoreUntyped(this IConfiguration configuration, object obj, Type type, Action<BinderOptions>? configureOptions)
         {
             if (configuration is null)
@@ -72,9 +79,9 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
 
             foreach (IConfigurationSection section in configuration.GetChildren())
             {
-                if (section.Value is string stringValue1)
+                if (section.Value is string value)
                 {
-                    obj.Add(ParseInt(stringValue1, () => section.Path)!);
+                    obj.Add(ParseInt(value, () => section.Path));
                 }
             }
         }
@@ -86,33 +93,11 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
                 throw new ArgumentNullException(nameof(obj));
             }
 
-            List<string>? temp = null;
-            foreach (IConfigurationSection section in configuration.GetChildren())
-            {
-                switch (section.Key)
-                {
-                    case "MyInt":
-                        {
-                            if (configuration["MyInt"] is string stringValue2)
-                            {
-                                obj.MyInt = ParseInt(stringValue2, () => section.Path)!;
-                            }
-                        }
-                        break;
-                    default:
-                        {
-                            if (binderOptions?.ErrorOnUnknownConfiguration == true)
-                            {
-                                (temp ??= new List<string>()).Add($"'{section.Key}'");
-                            }
-                        }
-                        break;
-                }
-            }
+            ValidateConfigurationKeys(typeof(Program.MyClass2), s_configKeys_ProgramMyClass2, configuration, binderOptions);
 
-            if (temp is not null)
+            if (configuration["MyInt"] is string value1)
             {
-                throw new InvalidOperationException($"'ErrorOnUnknownConfiguration' was set on the provided BinderOptions, but the following properties were not found on the instance of {typeof(Program.MyClass2)}: {string.Join(", ", temp)}");
+                obj.MyInt = ParseInt(value1, () => configuration.GetSection("MyInt").Path);
             }
         }
 
@@ -125,9 +110,9 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
 
             foreach (IConfigurationSection section in configuration.GetChildren())
             {
-                var element = new Program.MyClass2();
-                BindCore(section, ref element, binderOptions);
-                obj.Add(element);
+                var value = new Program.MyClass2();
+                BindCore(section, ref value, binderOptions);
+                obj.Add(value);
             }
         }
 
@@ -140,9 +125,9 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
 
             foreach (IConfigurationSection section in configuration.GetChildren())
             {
-                if (section.Value is string stringValue5)
+                if (section.Value is string value)
                 {
-                    obj[section.Key!] = stringValue5!;
+                    obj[section.Key] = value;
                 }
             }
         }
@@ -154,71 +139,57 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
                 throw new ArgumentNullException(nameof(obj));
             }
 
-            List<string>? temp = null;
-            foreach (IConfigurationSection section in configuration.GetChildren())
+            ValidateConfigurationKeys(typeof(Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions);
+
+            obj.MyString = configuration["MyString"]!;
+
+            if (configuration["MyInt"] is string value4)
             {
-                switch (section.Key)
-                {
-                    case "MyString":
-                        {
-                            obj.MyString = configuration["MyString"]!;
-                        }
-                        break;
-                    case "MyInt":
-                        {
-                            if (configuration["MyInt"] is string stringValue7)
-                            {
-                                obj.MyInt = ParseInt(stringValue7, () => section.Path)!;
-                            }
-                        }
-                        break;
-                    case "MyList":
-                        {
-                            if (HasChildren(section))
-                            {
-                                List<int> temp8 = obj.MyList;
-                                temp8 ??= new List<int>();
-                                BindCore(section, ref temp8, binderOptions);
-                                obj.MyList = temp8;
-                            }
-                        }
-                        break;
-                    case "MyList2":
-                        {
-                            if (HasChildren(section))
-                            {
-                                List<Program.MyClass2> temp9 = obj.MyList2;
-                                temp9 ??= new List<Program.MyClass2>();
-                                BindCore(section, ref temp9, binderOptions);
-                                obj.MyList2 = temp9;
-                            }
-                        }
-                        break;
-                    case "MyDictionary":
-                        {
-                            if (HasChildren(section))
-                            {
-                                Dictionary<string, string> temp10 = obj.MyDictionary;
-                                temp10 ??= new Dictionary<string, string>();
-                                BindCore(section, ref temp10, binderOptions);
-                                obj.MyDictionary = temp10;
-                            }
-                        }
-                        break;
-                    default:
-                        {
-                            if (binderOptions?.ErrorOnUnknownConfiguration == true)
-                            {
-                                (temp ??= new List<string>()).Add($"'{section.Key}'");
-                            }
-                        }
-                        break;
-                }
+                obj.MyInt = ParseInt(value4, () => configuration.GetSection("MyInt").Path);
+            }
+
+            if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section5)
+            {
+                List<int> temp7 = obj.MyList;
+                temp7 ??= new List<int>();
+                BindCore(section5, ref temp7, binderOptions);
+                obj.MyList = temp7;
             }
 
-            if (temp is not null)
+            if (AsConfigWithChildren(configuration.GetSection("MyList2")) is IConfigurationSection section8)
             {
-                throw new InvalidOperationException($"'ErrorOnUnknownConfiguration' was set on the provided BinderOptions, but the following properties were not found on the instance of {typeof(Program.MyClass)}: {string.Join(", ", temp)}");
+                List<Program.MyClass2> temp10 = obj.MyList2;
+                temp10 ??= new List<Program.MyClass2>();
+                BindCore(section8, ref temp10, binderOptions);
+                obj.MyList2 = temp10;
+            }
+
+            if (AsConfigWithChildren(configuration.GetSection("MyDictionary")) is IConfigurationSection section11)
+            {
+                Dictionary<string, string> temp13 = obj.MyDictionary;
+                temp13 ??= new Dictionary<string, string>();
+                BindCore(section11, ref temp13, binderOptions);
+                obj.MyDictionary = temp13;
+            }
+        }
+
+        /// <summary>If required by the binder options, validates that there are no unknown keys in the input configuration object.</summary>
+        public static void ValidateConfigurationKeys(Type type, Lazy<HashSet<string>> keys, IConfiguration configuration, BinderOptions? binderOptions)
+        {
+            if (binderOptions?.ErrorOnUnknownConfiguration is true)
+            {
+                List<string>? temp = null;
+                foreach (IConfigurationSection section in configuration.GetChildren())
+                {
+                    if (!keys.Value.Contains(section.Key))
+                    {
+                        (temp ??= new List<string>()).Add($"'{section.Key}'");
+                    }
+                }
+                if (temp is not null)
+                {
+                    throw new InvalidOperationException($"'ErrorOnUnknownConfiguration' was set on the provided BinderOptions, but the following properties were not found on the instance of {type}: {string.Join(", ", temp)}");
+                }
             }
         }
 
@@ -228,16 +199,16 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
             {
                 return true;
             }
-            return HasChildren(configuration);
+            return AsConfigWithChildren(configuration) is not null;
         }
 
-        public static bool HasChildren(IConfiguration configuration)
+        public static IConfiguration? AsConfigWithChildren(IConfiguration configuration)
         {
-            foreach (IConfigurationSection section in configuration.GetChildren())
+            foreach (IConfigurationSection _ in configuration.GetChildren())
             {
-                return true;
+                return configuration;
             }
-            return false;
+            return null;
         }
 
         public static BinderOptions? GetBinderOptions(Action<BinderOptions>? configureOptions)
@@ -255,11 +226,11 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
             return binderOptions;
         }
 
-        public static int ParseInt(string stringValue, Func<string?> getPath)
+        public static int ParseInt(string value, Func<string?> getPath)
         {
             try
             {
-                return int.Parse(stringValue, NumberStyles.Integer, CultureInfo.InvariantCulture);
+                return int.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture);
             }
             catch (Exception exception)
             {
index 016995b..26cb4aa 100644 (file)
@@ -1,7 +1,9 @@
 // <auto-generated/>
 #nullable enable
+#pragma warning disable CS0612, CS0618 // Suppress warnings about [Obsolete] member usage in generated code.
 
 /// <summary>Generated helper providing an AOT and linking compatible implementation for configuration binding.</summary>
+[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")]
 internal static class GeneratedServiceCollectionBinder
 {
     /// <summary>Registers a configuration instance which TOptions will bind against.</summary>
@@ -33,12 +35,17 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
 {
     using Microsoft.Extensions.Configuration;
     using System;
+    using System.CodeDom.Compiler;
     using System.Collections.Generic;
     using System.Globalization;
 
     /// <summary>Provide core binding logic.</summary>
-    internal static class CoreBindingHelper
+    [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")]
+    file static class CoreBindingHelper
     {
+        private readonly static Lazy<HashSet<string>> s_configKeys_ProgramMyClass2 = new(() => new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "MyInt" });
+        private readonly static Lazy<HashSet<string>> s_configKeys_ProgramMyClass = new(() => new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "MyString", "MyInt", "MyList", "MyList2", "MyDictionary" });
+
         public static void BindCoreUntyped(this IConfiguration configuration, object obj, Type type, Action<BinderOptions>? configureOptions)
         {
             if (configuration is null)
@@ -72,9 +79,9 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
 
             foreach (IConfigurationSection section in configuration.GetChildren())
             {
-                if (section.Value is string stringValue1)
+                if (section.Value is string value)
                 {
-                    obj.Add(ParseInt(stringValue1, () => section.Path)!);
+                    obj.Add(ParseInt(value, () => section.Path));
                 }
             }
         }
@@ -86,33 +93,11 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
                 throw new ArgumentNullException(nameof(obj));
             }
 
-            List<string>? temp = null;
-            foreach (IConfigurationSection section in configuration.GetChildren())
-            {
-                switch (section.Key)
-                {
-                    case "MyInt":
-                        {
-                            if (configuration["MyInt"] is string stringValue2)
-                            {
-                                obj.MyInt = ParseInt(stringValue2, () => section.Path)!;
-                            }
-                        }
-                        break;
-                    default:
-                        {
-                            if (binderOptions?.ErrorOnUnknownConfiguration == true)
-                            {
-                                (temp ??= new List<string>()).Add($"'{section.Key}'");
-                            }
-                        }
-                        break;
-                }
-            }
+            ValidateConfigurationKeys(typeof(Program.MyClass2), s_configKeys_ProgramMyClass2, configuration, binderOptions);
 
-            if (temp is not null)
+            if (configuration["MyInt"] is string value1)
             {
-                throw new InvalidOperationException($"'ErrorOnUnknownConfiguration' was set on the provided BinderOptions, but the following properties were not found on the instance of {typeof(Program.MyClass2)}: {string.Join(", ", temp)}");
+                obj.MyInt = ParseInt(value1, () => configuration.GetSection("MyInt").Path);
             }
         }
 
@@ -125,9 +110,9 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
 
             foreach (IConfigurationSection section in configuration.GetChildren())
             {
-                var element = new Program.MyClass2();
-                BindCore(section, ref element, binderOptions);
-                obj.Add(element);
+                var value = new Program.MyClass2();
+                BindCore(section, ref value, binderOptions);
+                obj.Add(value);
             }
         }
 
@@ -140,9 +125,9 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
 
             foreach (IConfigurationSection section in configuration.GetChildren())
             {
-                if (section.Value is string stringValue5)
+                if (section.Value is string value)
                 {
-                    obj[section.Key!] = stringValue5!;
+                    obj[section.Key] = value;
                 }
             }
         }
@@ -154,71 +139,57 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
                 throw new ArgumentNullException(nameof(obj));
             }
 
-            List<string>? temp = null;
-            foreach (IConfigurationSection section in configuration.GetChildren())
+            ValidateConfigurationKeys(typeof(Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions);
+
+            obj.MyString = configuration["MyString"]!;
+
+            if (configuration["MyInt"] is string value4)
             {
-                switch (section.Key)
-                {
-                    case "MyString":
-                        {
-                            obj.MyString = configuration["MyString"]!;
-                        }
-                        break;
-                    case "MyInt":
-                        {
-                            if (configuration["MyInt"] is string stringValue7)
-                            {
-                                obj.MyInt = ParseInt(stringValue7, () => section.Path)!;
-                            }
-                        }
-                        break;
-                    case "MyList":
-                        {
-                            if (HasChildren(section))
-                            {
-                                List<int> temp8 = obj.MyList;
-                                temp8 ??= new List<int>();
-                                BindCore(section, ref temp8, binderOptions);
-                                obj.MyList = temp8;
-                            }
-                        }
-                        break;
-                    case "MyList2":
-                        {
-                            if (HasChildren(section))
-                            {
-                                List<Program.MyClass2> temp9 = obj.MyList2;
-                                temp9 ??= new List<Program.MyClass2>();
-                                BindCore(section, ref temp9, binderOptions);
-                                obj.MyList2 = temp9;
-                            }
-                        }
-                        break;
-                    case "MyDictionary":
-                        {
-                            if (HasChildren(section))
-                            {
-                                Dictionary<string, string> temp10 = obj.MyDictionary;
-                                temp10 ??= new Dictionary<string, string>();
-                                BindCore(section, ref temp10, binderOptions);
-                                obj.MyDictionary = temp10;
-                            }
-                        }
-                        break;
-                    default:
-                        {
-                            if (binderOptions?.ErrorOnUnknownConfiguration == true)
-                            {
-                                (temp ??= new List<string>()).Add($"'{section.Key}'");
-                            }
-                        }
-                        break;
-                }
+                obj.MyInt = ParseInt(value4, () => configuration.GetSection("MyInt").Path);
+            }
+
+            if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section5)
+            {
+                List<int> temp7 = obj.MyList;
+                temp7 ??= new List<int>();
+                BindCore(section5, ref temp7, binderOptions);
+                obj.MyList = temp7;
             }
 
-            if (temp is not null)
+            if (AsConfigWithChildren(configuration.GetSection("MyList2")) is IConfigurationSection section8)
             {
-                throw new InvalidOperationException($"'ErrorOnUnknownConfiguration' was set on the provided BinderOptions, but the following properties were not found on the instance of {typeof(Program.MyClass)}: {string.Join(", ", temp)}");
+                List<Program.MyClass2> temp10 = obj.MyList2;
+                temp10 ??= new List<Program.MyClass2>();
+                BindCore(section8, ref temp10, binderOptions);
+                obj.MyList2 = temp10;
+            }
+
+            if (AsConfigWithChildren(configuration.GetSection("MyDictionary")) is IConfigurationSection section11)
+            {
+                Dictionary<string, string> temp13 = obj.MyDictionary;
+                temp13 ??= new Dictionary<string, string>();
+                BindCore(section11, ref temp13, binderOptions);
+                obj.MyDictionary = temp13;
+            }
+        }
+
+        /// <summary>If required by the binder options, validates that there are no unknown keys in the input configuration object.</summary>
+        public static void ValidateConfigurationKeys(Type type, Lazy<HashSet<string>> keys, IConfiguration configuration, BinderOptions? binderOptions)
+        {
+            if (binderOptions?.ErrorOnUnknownConfiguration is true)
+            {
+                List<string>? temp = null;
+                foreach (IConfigurationSection section in configuration.GetChildren())
+                {
+                    if (!keys.Value.Contains(section.Key))
+                    {
+                        (temp ??= new List<string>()).Add($"'{section.Key}'");
+                    }
+                }
+                if (temp is not null)
+                {
+                    throw new InvalidOperationException($"'ErrorOnUnknownConfiguration' was set on the provided BinderOptions, but the following properties were not found on the instance of {type}: {string.Join(", ", temp)}");
+                }
             }
         }
 
@@ -228,16 +199,16 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
             {
                 return true;
             }
-            return HasChildren(configuration);
+            return AsConfigWithChildren(configuration) is not null;
         }
 
-        public static bool HasChildren(IConfiguration configuration)
+        public static IConfiguration? AsConfigWithChildren(IConfiguration configuration)
         {
-            foreach (IConfigurationSection section in configuration.GetChildren())
+            foreach (IConfigurationSection _ in configuration.GetChildren())
             {
-                return true;
+                return configuration;
             }
-            return false;
+            return null;
         }
 
         public static BinderOptions? GetBinderOptions(Action<BinderOptions>? configureOptions)
@@ -255,11 +226,11 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
             return binderOptions;
         }
 
-        public static int ParseInt(string stringValue, Func<string?> getPath)
+        public static int ParseInt(string value, Func<string?> getPath)
         {
             try
             {
-                return int.Parse(stringValue, NumberStyles.Integer, CultureInfo.InvariantCulture);
+                return int.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture);
             }
             catch (Exception exception)
             {
index 312a553..53f8ead 100644 (file)
@@ -1,7 +1,9 @@
 // <auto-generated/>
 #nullable enable
+#pragma warning disable CS0612, CS0618 // Suppress warnings about [Obsolete] member usage in generated code.
 
 /// <summary>Generated helper providing an AOT and linking compatible implementation for configuration binding.</summary>
+[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")]
 internal static class GeneratedServiceCollectionBinder
 {
     /// <summary>Registers a configuration instance which TOptions will bind against.</summary>
@@ -27,12 +29,17 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
 {
     using Microsoft.Extensions.Configuration;
     using System;
+    using System.CodeDom.Compiler;
     using System.Collections.Generic;
     using System.Globalization;
 
     /// <summary>Provide core binding logic.</summary>
-    internal static class CoreBindingHelper
+    [GeneratedCode("Microsoft.Extensions.Configuration.Binder.SourceGeneration", "42.42.42.42")]
+    file static class CoreBindingHelper
     {
+        private readonly static Lazy<HashSet<string>> s_configKeys_ProgramMyClass2 = new(() => new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "MyInt" });
+        private readonly static Lazy<HashSet<string>> s_configKeys_ProgramMyClass = new(() => new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "MyString", "MyInt", "MyList", "MyList2", "MyDictionary" });
+
         public static void BindCoreUntyped(this IConfiguration configuration, object obj, Type type, Action<BinderOptions>? configureOptions)
         {
             if (configuration is null)
@@ -66,9 +73,9 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
 
             foreach (IConfigurationSection section in configuration.GetChildren())
             {
-                if (section.Value is string stringValue1)
+                if (section.Value is string value)
                 {
-                    obj.Add(ParseInt(stringValue1, () => section.Path)!);
+                    obj.Add(ParseInt(value, () => section.Path));
                 }
             }
         }
@@ -80,33 +87,11 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
                 throw new ArgumentNullException(nameof(obj));
             }
 
-            List<string>? temp = null;
-            foreach (IConfigurationSection section in configuration.GetChildren())
-            {
-                switch (section.Key)
-                {
-                    case "MyInt":
-                        {
-                            if (configuration["MyInt"] is string stringValue2)
-                            {
-                                obj.MyInt = ParseInt(stringValue2, () => section.Path)!;
-                            }
-                        }
-                        break;
-                    default:
-                        {
-                            if (binderOptions?.ErrorOnUnknownConfiguration == true)
-                            {
-                                (temp ??= new List<string>()).Add($"'{section.Key}'");
-                            }
-                        }
-                        break;
-                }
-            }
+            ValidateConfigurationKeys(typeof(Program.MyClass2), s_configKeys_ProgramMyClass2, configuration, binderOptions);
 
-            if (temp is not null)
+            if (configuration["MyInt"] is string value1)
             {
-                throw new InvalidOperationException($"'ErrorOnUnknownConfiguration' was set on the provided BinderOptions, but the following properties were not found on the instance of {typeof(Program.MyClass2)}: {string.Join(", ", temp)}");
+                obj.MyInt = ParseInt(value1, () => configuration.GetSection("MyInt").Path);
             }
         }
 
@@ -119,9 +104,9 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
 
             foreach (IConfigurationSection section in configuration.GetChildren())
             {
-                var element = new Program.MyClass2();
-                BindCore(section, ref element, binderOptions);
-                obj.Add(element);
+                var value = new Program.MyClass2();
+                BindCore(section, ref value, binderOptions);
+                obj.Add(value);
             }
         }
 
@@ -134,9 +119,9 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
 
             foreach (IConfigurationSection section in configuration.GetChildren())
             {
-                if (section.Value is string stringValue5)
+                if (section.Value is string value)
                 {
-                    obj[section.Key!] = stringValue5!;
+                    obj[section.Key] = value;
                 }
             }
         }
@@ -148,71 +133,57 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
                 throw new ArgumentNullException(nameof(obj));
             }
 
-            List<string>? temp = null;
-            foreach (IConfigurationSection section in configuration.GetChildren())
+            ValidateConfigurationKeys(typeof(Program.MyClass), s_configKeys_ProgramMyClass, configuration, binderOptions);
+
+            obj.MyString = configuration["MyString"]!;
+
+            if (configuration["MyInt"] is string value4)
             {
-                switch (section.Key)
-                {
-                    case "MyString":
-                        {
-                            obj.MyString = configuration["MyString"]!;
-                        }
-                        break;
-                    case "MyInt":
-                        {
-                            if (configuration["MyInt"] is string stringValue7)
-                            {
-                                obj.MyInt = ParseInt(stringValue7, () => section.Path)!;
-                            }
-                        }
-                        break;
-                    case "MyList":
-                        {
-                            if (HasChildren(section))
-                            {
-                                List<int> temp8 = obj.MyList;
-                                temp8 ??= new List<int>();
-                                BindCore(section, ref temp8, binderOptions);
-                                obj.MyList = temp8;
-                            }
-                        }
-                        break;
-                    case "MyList2":
-                        {
-                            if (HasChildren(section))
-                            {
-                                List<Program.MyClass2> temp9 = obj.MyList2;
-                                temp9 ??= new List<Program.MyClass2>();
-                                BindCore(section, ref temp9, binderOptions);
-                                obj.MyList2 = temp9;
-                            }
-                        }
-                        break;
-                    case "MyDictionary":
-                        {
-                            if (HasChildren(section))
-                            {
-                                Dictionary<string, string> temp10 = obj.MyDictionary;
-                                temp10 ??= new Dictionary<string, string>();
-                                BindCore(section, ref temp10, binderOptions);
-                                obj.MyDictionary = temp10;
-                            }
-                        }
-                        break;
-                    default:
-                        {
-                            if (binderOptions?.ErrorOnUnknownConfiguration == true)
-                            {
-                                (temp ??= new List<string>()).Add($"'{section.Key}'");
-                            }
-                        }
-                        break;
-                }
+                obj.MyInt = ParseInt(value4, () => configuration.GetSection("MyInt").Path);
+            }
+
+            if (AsConfigWithChildren(configuration.GetSection("MyList")) is IConfigurationSection section5)
+            {
+                List<int> temp7 = obj.MyList;
+                temp7 ??= new List<int>();
+                BindCore(section5, ref temp7, binderOptions);
+                obj.MyList = temp7;
             }
 
-            if (temp is not null)
+            if (AsConfigWithChildren(configuration.GetSection("MyList2")) is IConfigurationSection section8)
             {
-                throw new InvalidOperationException($"'ErrorOnUnknownConfiguration' was set on the provided BinderOptions, but the following properties were not found on the instance of {typeof(Program.MyClass)}: {string.Join(", ", temp)}");
+                List<Program.MyClass2> temp10 = obj.MyList2;
+                temp10 ??= new List<Program.MyClass2>();
+                BindCore(section8, ref temp10, binderOptions);
+                obj.MyList2 = temp10;
+            }
+
+            if (AsConfigWithChildren(configuration.GetSection("MyDictionary")) is IConfigurationSection section11)
+            {
+                Dictionary<string, string> temp13 = obj.MyDictionary;
+                temp13 ??= new Dictionary<string, string>();
+                BindCore(section11, ref temp13, binderOptions);
+                obj.MyDictionary = temp13;
+            }
+        }
+
+        /// <summary>If required by the binder options, validates that there are no unknown keys in the input configuration object.</summary>
+        public static void ValidateConfigurationKeys(Type type, Lazy<HashSet<string>> keys, IConfiguration configuration, BinderOptions? binderOptions)
+        {
+            if (binderOptions?.ErrorOnUnknownConfiguration is true)
+            {
+                List<string>? temp = null;
+                foreach (IConfigurationSection section in configuration.GetChildren())
+                {
+                    if (!keys.Value.Contains(section.Key))
+                    {
+                        (temp ??= new List<string>()).Add($"'{section.Key}'");
+                    }
+                }
+                if (temp is not null)
+                {
+                    throw new InvalidOperationException($"'ErrorOnUnknownConfiguration' was set on the provided BinderOptions, but the following properties were not found on the instance of {type}: {string.Join(", ", temp)}");
+                }
             }
         }
 
@@ -222,16 +193,16 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
             {
                 return true;
             }
-            return HasChildren(configuration);
+            return AsConfigWithChildren(configuration) is not null;
         }
 
-        public static bool HasChildren(IConfiguration configuration)
+        public static IConfiguration? AsConfigWithChildren(IConfiguration configuration)
         {
-            foreach (IConfigurationSection section in configuration.GetChildren())
+            foreach (IConfigurationSection _ in configuration.GetChildren())
             {
-                return true;
+                return configuration;
             }
-            return false;
+            return null;
         }
 
         public static BinderOptions? GetBinderOptions(Action<BinderOptions>? configureOptions)
@@ -249,11 +220,11 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
             return binderOptions;
         }
 
-        public static int ParseInt(string stringValue, Func<string?> getPath)
+        public static int ParseInt(string value, Func<string?> getPath)
         {
             try
             {
-                return int.Parse(stringValue, NumberStyles.Integer, CultureInfo.InvariantCulture);
+                return int.Parse(value, NumberStyles.Integer, CultureInfo.InvariantCulture);
             }
             catch (Exception exception)
             {
index f6bd083..ed93f66 100644 (file)
@@ -12,13 +12,14 @@ using Microsoft.CodeAnalysis;
 using Microsoft.CodeAnalysis.CSharp;
 using Microsoft.Extensions.DependencyInjection;
 using Microsoft.Extensions.Options;
+using Microsoft.Extensions.SourceGeneration.Configuration.Binder.Tests;
 using SourceGenerators.Tests;
 using Xunit;
 
 namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration.Tests
 {
     [ActiveIssue("https://github.com/dotnet/runtime/issues/52062", TestPlatforms.Browser)]
-    public partial class ConfigurationBindingGeneratorTests
+    public partial class ConfigurationBindingGeneratorTests : ConfigurationBinderTestsBase
     {
         private static class Diagnostics
         {
@@ -188,6 +189,59 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration.Tests
             }
         }
 
+        [Fact]
+        public async Task BindCanParseMethodParam()
+        {
+            string source = """
+                using System;
+                using Microsoft.AspNetCore.Builder;
+                using Microsoft.Extensions.Configuration;
+                using Microsoft.Extensions.DependencyInjection;
+
+                public class Program
+                {
+                    public static void Main()
+                    {
+                        ConfigurationBuilder configurationBuilder = new();
+                        IConfiguration config = configurationBuilder.Build();
+
+                        BindOptions(config, new MyClass0());
+                        BindOptions(config, new MyClass1(), (_) => { });
+                        BindOptions(config, "", new MyClass2());
+                    }
+
+                    private void BindOptions(IConfiguration config, MyClass0 instance)
+                    {
+                        config.Bind(instance);
+                    }
+
+                    private void BindOptions(IConfiguration config, MyClass1 instance, Action<BinderOptions>? configureOptions)
+                    {
+                        config.Bind(instance, configureOptions);
+                    }
+
+                    private void BindOptions(IConfiguration config, string path, MyClass2 instance)
+                    {
+                        config.Bind(path, instance);
+                    }
+
+                    public class MyClass0 { }
+                    public class MyClass1 { }
+                    public class MyClass2 { }
+                }
+                """;
+
+            var (d, r) = await RunGenerator(source);
+            Assert.Single(r);
+
+            string generatedSource = string.Join('\n', r[0].SourceText.Lines.Select(x => x.ToString()));
+            Assert.Contains($"public static void Bind(this global::Microsoft.Extensions.Configuration.IConfiguration configuration, global::Program.MyClass0 obj) => {{ }};", generatedSource);
+            Assert.Contains($"public static void Bind(this global::Microsoft.Extensions.Configuration.IConfiguration configuration, global::Program.MyClass1 obj, global::System.Action<global::Microsoft.Extensions.Configuration.BinderOptions>? configureOptions) => {{ }};", generatedSource);
+            Assert.Contains($"public static void Bind(this global::Microsoft.Extensions.Configuration.IConfiguration configuration, string key, global::Program.MyClass2 obj) => {{ }};", generatedSource);
+
+            Assert.Empty(d);
+        }
+
         private static async Task VerifyAgainstBaselineUsingFile(
             string filename,
             string testSourceCode,
@@ -203,12 +257,14 @@ namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration.Tests
                                              .Split(Environment.NewLine);
 
             var (d, r) = await RunGenerator(testSourceCode, languageVersion);
+            bool success = RoslynTestUtils.CompareLines(expectedLines, r[0].SourceText,
+                out string errorMessage);
 
+#if !SKIP_BASELINES
             Assert.Single(r);
             (assessDiagnostics ?? ((d) => Assert.Empty(d))).Invoke(d);
-
-            Assert.True(RoslynTestUtils.CompareLines(expectedLines, r[0].SourceText,
-                out string errorMessage), errorMessage);
+            Assert.True(success, errorMessage);
+#endif
         }
 
         private static async Task<(ImmutableArray<Diagnostic>, ImmutableArray<GeneratedSourceResult>)> RunGenerator(
index e139090..b67b5ba 100644 (file)
@@ -2,13 +2,18 @@
   <PropertyGroup>
     <TargetFrameworks>$(NetCoreAppCurrent);$(NetFrameworkMinimum)</TargetFrameworks>
     <EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
-    <DefineConstants>$(DefineConstants);BUILDING_SOURCE_GENERATOR_TESTS;ROSLYN4_0_OR_GREATER;ROSLYN4_4_OR_GREATER</DefineConstants>
     <!-- Type not supported; property on type not suppported; value types invalid for bind; generator could not parse target type -->
     <NoWarn>SYSLIB1100,SYSLIB1101,SYSLIB1103,SYSLIB1104</NoWarn>
     <!-- The SDK disables the configuration binding generator by default no matter how it was referenced, so we need to enable it here for testing. -->
     <EnableConfigurationBindingGenerator>true</EnableConfigurationBindingGenerator>
   </PropertyGroup>
 
+  <PropertyGroup>
+    <DefineConstants>$(DefineConstants);BUILDING_SOURCE_GENERATOR_TESTS;ROSLYN4_0_OR_GREATER;ROSLYN4_4_OR_GREATER</DefineConstants>
+    <DefineConstants Condition="'$(LaunchTestDebugger)' == 'true'">$(DefineConstants);LAUNCH_DEBUGGER</DefineConstants>
+    <DefineConstants Condition="'$(SkipBaselines)' == 'true'">$(DefineConstants);SKIP_BASELINES</DefineConstants>
+  </PropertyGroup>
+
   <ItemGroup Condition="'$(TargetFrameworkIdentifier)' != '.NETCoreApp'">
     <Compile Include="$(CoreLibSharedDir)System\Runtime\Versioning\RequiresPreviewFeaturesAttribute.cs" Link="System\Runtime\Versioning\RequiresPreviewFeaturesAttribute.cs" />
   </ItemGroup>