Skip to content

Commit

Permalink
Fix Binding with IDictionary<,> implementer types (#77582)
Browse files Browse the repository at this point in the history
  • Loading branch information
tarekgh committed Oct 30, 2022
1 parent 6893509 commit e6700ea
Show file tree
Hide file tree
Showing 2 changed files with 70 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -554,9 +554,9 @@ private static bool CanBindToTheseConstructorParameters(ParameterInfo[] construc
// Binds and potentially overwrites a concrete dictionary.
// This differs from BindDictionaryInterface because this method doesn't clone
// the dictionary; it sets and/or overwrites values directly.
// When a user specifies a concrete dictionary in their config class, then that
// value is used as-us. When a user specifies an interface (instantiated) in their config class,
// then it is cloned to a new dictionary, the same way as other collections.
// When a user specifies a concrete dictionary or a concrete class implementing IDictionary<,>
// in their config class, then that value is used as-is. When a user specifies an interface (instantiated)
// in their config class, then it is cloned to a new dictionary, the same way as other collections.
[RequiresDynamicCode(DynamicCodeWarningMessage)]
[RequiresUnreferencedCode("Cannot statically analyze what the element type is of the value objects in the dictionary so its members may be trimmed.")]
private static void BindConcreteDictionary(
Expand Down Expand Up @@ -584,10 +584,17 @@ private static bool CanBindToTheseConstructorParameters(ParameterInfo[] construc
return;
}

Type genericType = typeof(Dictionary<,>).MakeGenericType(keyType, valueType);
MethodInfo tryGetValue = dictionaryType.GetMethod("TryGetValue", BindingFlags.Public | BindingFlags.Instance)!;

Debug.Assert(dictionary is not null);
// dictionary should be of type Dictionary<,> or of type implementing IDictionary<,>
PropertyInfo? setter = dictionary.GetType().GetProperty("Item", BindingFlags.Public | BindingFlags.Instance);
if (setter is null || !setter.CanWrite)
{
// Cannot set any item on the dictionary object.
return;
}

MethodInfo tryGetValue = dictionaryType.GetMethod("TryGetValue")!;
PropertyInfo setter = genericType.GetProperty("Item", DeclaredOnlyLookup)!;
foreach (IConfigurationSection child in config.GetChildren())
{
try
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1067,7 +1067,7 @@ public void CanBindInitializedIEnumerableAndTheOriginalItemsAreNotMutated()
{"AlreadyInitializedIEnumerableInterface:1", "val1"},
{"AlreadyInitializedIEnumerableInterface:2", "val2"},
{"AlreadyInitializedIEnumerableInterface:x", "valx"},

{"ICollectionNoSetter:0", "val0"},
{"ICollectionNoSetter:1", "val1"},
};
Expand Down Expand Up @@ -1602,5 +1602,61 @@ private class OptionsWithInterdependentProperties
public IEnumerable<int> FilteredConfigValues => ConfigValues.Where(p => p > 10);
public IEnumerable<int> ConfigValues { get; set; }
}

[Fact]
public void DifferentDictionaryBindingCasesTest()
{
var dic = new Dictionary<string, string>() { { "key", "value" } };
var config = new ConfigurationBuilder()
.AddInMemoryCollection(dic)
.Build();

Assert.Single(config.Get<Dictionary<string, string>>());
Assert.Single(config.Get<IDictionary<string, string>>());
Assert.Single(config.Get<ExtendedDictionary<string, string>>());
Assert.Single(config.Get<ImplementerOfIDictionaryClass<string, string>>());
}

public class ImplementerOfIDictionaryClass<TKey, TValue> : IDictionary<TKey, TValue>
{
private Dictionary<TKey, TValue> _dict = new();

public TValue this[TKey key] { get => _dict[key]; set => _dict[key] = value; }

public ICollection<TKey> Keys => _dict.Keys;

public ICollection<TValue> Values => _dict.Values;

public int Count => _dict.Count;

public bool IsReadOnly => false;

public void Add(TKey key, TValue value) => _dict.Add(key, value);

public void Add(KeyValuePair<TKey, TValue> item) => _dict.Add(item.Key, item.Value);

public void Clear() => _dict.Clear();

public bool Contains(KeyValuePair<TKey, TValue> item) => _dict.Contains(item);

public bool ContainsKey(TKey key) => _dict.ContainsKey(key);

public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex) => throw new NotImplementedException();

public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator() => _dict.GetEnumerator();

public bool Remove(TKey key) => _dict.Remove(key);

public bool Remove(KeyValuePair<TKey, TValue> item) => _dict.Remove(item.Key);

public bool TryGetValue(TKey key, out TValue value) => _dict.TryGetValue(key, out value);

System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() => _dict.GetEnumerator();
}

public class ExtendedDictionary<TKey, TValue> : Dictionary<TKey, TValue>
{

}
}
}

0 comments on commit e6700ea

Please sign in to comment.