Files
NLog-NLog/src/NLog/Internal/ObjectReflectionCache.cs
T

895 lines
37 KiB
C#

//
// Copyright (c) 2004-2024 Jaroslaw Kowalski <jaak@jkowalski.net>, Kim Christensen, Julian Verdurmen
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
//
// * Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// * Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// * Neither the name of Jaroslaw Kowalski nor the names of its
// contributors may be used to endorse or promote products derived from this
// software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
// THE POSSIBILITY OF SUCH DAMAGE.
//
namespace NLog.Internal
{
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Reflection;
using NLog.Common;
using NLog.Config;
/// <summary>
/// Converts object into a List of property-names and -values using reflection
/// </summary>
internal sealed class ObjectReflectionCache : IObjectTypeTransformer
{
private MruCache<Type, ObjectPropertyInfos> ObjectTypeCache => _objectTypeCache ?? System.Threading.Interlocked.CompareExchange(ref _objectTypeCache, new MruCache<Type, ObjectPropertyInfos>(10000), null) ?? _objectTypeCache;
private MruCache<Type, ObjectPropertyInfos>? _objectTypeCache;
private readonly IServiceProvider _serviceProvider;
private IObjectTypeTransformer ObjectTypeTransformation => _objectTypeTransformation ?? (_objectTypeTransformation = _serviceProvider?.GetService<IObjectTypeTransformer>() ?? this);
private IObjectTypeTransformer? _objectTypeTransformation;
public ObjectReflectionCache(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
object? IObjectTypeTransformer.TryTransformObject(object? obj)
{
return null;
}
public ObjectPropertyList LookupObjectProperties(object value) => LookupObjectProperties(value, false);
/// <summary>
/// Lookup object properties, optionally falling back to public fields when the object has no public properties.
/// </summary>
public ObjectPropertyList LookupObjectProperties(object value, bool includeFields)
{
if (TryLookupExpandoObject(value, includeFields, out var propertyValues))
{
return propertyValues;
}
if (!ReferenceEquals(ObjectTypeTransformation, this))
{
var result = ObjectTypeTransformation.TryTransformObject(value);
if (result != null)
{
if (ConvertSimpleToString(result))
{
return new ObjectPropertyList(result, ObjectPropertyInfos.SimpleToString.Properties, ObjectPropertyInfos.SimpleToString.FastLookup);
}
if (TryLookupExpandoObject(result, out propertyValues))
{
return propertyValues;
}
value = result;
}
}
var objectType = value.GetType();
var propertyInfos = CheckForObjectProperties(value) ? BuildObjectPropertyInfos(value, includeFields) : ObjectPropertyInfos.SimpleToString;
if (includeFields && propertyInfos.FieldLookup is null)
{
// Remember that the public fields were resolved, so the type is not reflected again
propertyInfos = new ObjectPropertyInfos(propertyInfos.Properties, propertyInfos.FastLookup, ArrayHelper.Empty<FastPropertyLookup>());
}
ObjectTypeCache.TryAddValue(objectType, propertyInfos);
return propertyInfos.CreatePropertyList(value, includeFields);
}
/// <summary>
/// Try get value from <paramref name="value"/>, using <paramref name="objectPath"/>, and set into <paramref name="foundValue"/>
/// </summary>
public bool TryGetObjectProperty(object? value, string[]? objectPath, out object? foundValue)
{
foundValue = null;
if (objectPath is null)
{
return false;
}
for (int i = 0; i < objectPath.Length; ++i)
{
if (value is null)
{
return false;
}
var propertyList = LookupObjectProperties(value);
if (propertyList.TryGetPropertyValue(objectPath[i], out var propertyValue))
{
value = propertyValue.Value;
}
else
{
foundValue = null;
return false; //Wrong, but done
}
}
foundValue = value;
return true;
}
public bool TryLookupExpandoObject(object value, out ObjectPropertyList objectPropertyList)
=> TryLookupExpandoObject(value, false, out objectPropertyList);
private bool TryLookupExpandoObject(object value, bool includeFields, out ObjectPropertyList objectPropertyList)
{
if (value is IDictionary<string, object> expando)
{
objectPropertyList = new ObjectPropertyList(expando);
return true;
}
#if !NET35 && !NET40 && NETFRAMEWORK
if (value is System.Dynamic.DynamicObject d)
{
var dictionary = DynamicObjectToDict(d);
objectPropertyList = new ObjectPropertyList(dictionary);
return true;
}
#endif
Type objectType = value.GetType();
if (ObjectTypeCache.TryGetValue(objectType, out var propertyInfos) && !(includeFields && propertyInfos.NeedsFieldLookup))
{
if (!propertyInfos.HasFastLookup)
{
var fastLookup = BuildFastLookup(propertyInfos.Properties, false);
propertyInfos = new ObjectPropertyInfos(propertyInfos.Properties, fastLookup, propertyInfos.FieldLookup);
ObjectTypeCache.TryAddValue(objectType, propertyInfos);
}
objectPropertyList = propertyInfos.CreatePropertyList(value, includeFields);
return true;
}
var dictionaryEnumerator = TryGetDictionaryEnumerator(value);
if (dictionaryEnumerator != null)
{
var dictionaryInfos = new ObjectPropertyInfos(ArrayHelper.Empty<PropertyInfo>(), new[] { new FastPropertyLookup(string.Empty, TypeCode.Object, (o, p) => dictionaryEnumerator.GetEnumerator(o)) });
ObjectTypeCache.TryAddValue(objectType, dictionaryInfos);
objectPropertyList = new ObjectPropertyList(value, dictionaryInfos.Properties, dictionaryInfos.FastLookup);
return true;
}
objectPropertyList = default(ObjectPropertyList);
return false;
}
[UnconditionalSuppressMessage("Trimming - Allow reflection of message args", "IL2072")]
private static ObjectPropertyInfos BuildObjectPropertyInfos(object value, bool includeFields)
{
var objectType = value.GetType();
var properties = GetPublicProperties(objectType);
if (value is Exception)
{
// Special handling of Exception (Include Exception-Type as artificial first property)
var fastLookup = BuildFastLookup(properties, true);
return new ObjectPropertyInfos(properties, fastLookup);
}
else if (properties.Length == 0)
{
if (!includeFields)
return ObjectPropertyInfos.SimpleToString;
// Nothing to show as properties, so keep the public fields aside for the IncludePublicFields fallback
var fields = GetPublicFields(objectType);
return new ObjectPropertyInfos(ArrayHelper.Empty<PropertyInfo>(), ArrayHelper.Empty<FastPropertyLookup>(), BuildFieldLookup(fields));
}
else
{
return new ObjectPropertyInfos(properties, null);
}
}
private static bool CheckForObjectProperties(object objectValue)
{
if (ConvertSimpleToString(objectValue))
return false;
if (objectValue is Uri)
return false;
if (objectValue is Delegate)
return false; // Skip serializing all types in the application
if (objectValue is MemberInfo)
return false; // Skip serializing all types in the application
if (objectValue is Assembly)
return false; // Skip serializing all types in the application
if (objectValue is Module)
return false; // Skip serializing all types in the application
if (objectValue is System.IO.Stream)
return false; // Skip serializing properties that often throws exceptions
return true;
}
private static bool ConvertSimpleToString(object objectValue)
{
if (objectValue is IFormattable)
return true;
if (objectValue is IConvertible convertible)
return convertible.GetTypeCode() != TypeCode.Object;
return false;
}
private static PropertyInfo[] GetPublicProperties([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] Type type)
{
PropertyInfo[]? properties = null;
try
{
properties = type.GetProperties(BindingFlags.Instance | BindingFlags.Public);
}
catch (Exception ex)
{
InternalLogger.Warn(ex, "Failed to get object properties for type: {0}", type);
}
// Skip Index-Item-Properties (Ex. public string this[int Index])
if (properties != null)
{
foreach (var prop in properties)
{
if (!prop.IsValidPublicProperty())
{
properties = properties.Where(p => p.IsValidPublicProperty()).ToArray();
break;
}
}
}
return properties ?? ArrayHelper.Empty<PropertyInfo>();
}
private static FieldInfo[] GetPublicFields([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields)] Type type)
{
try
{
return type.GetFields(BindingFlags.Instance | BindingFlags.Public);
}
catch (Exception ex)
{
InternalLogger.Warn(ex, "Failed to get object fields for type: {0}", type);
return ArrayHelper.Empty<FieldInfo>();
}
}
private static FastPropertyLookup[] BuildFieldLookup(FieldInfo[] fields)
{
FastPropertyLookup[] fastLookup = new FastPropertyLookup[fields.Length];
int count = 0;
for (int i = 0; i < fields.Length; ++i)
{
var field = fields[i];
if (IsHiddenByDerivedField(field, fields))
continue; // A derived type redeclared the name with "new", so the member is written once
TypeCode typeCode = Type.GetTypeCode(field.FieldType);
fastLookup[count++] = new FastPropertyLookup(field.Name, typeCode, (o, p) => field.GetValue(o));
}
if (count != fastLookup.Length)
Array.Resize(ref fastLookup, count);
return fastLookup;
}
private static bool IsHiddenByDerivedField(FieldInfo field, FieldInfo[] fields)
{
foreach (var other in fields)
{
if (!ReferenceEquals(other, field) && other.Name == field.Name && other.DeclaringType != null && field.DeclaringType != null && other.DeclaringType.IsSubclassOf(field.DeclaringType))
return true;
}
return false;
}
private static FastPropertyLookup[] BuildFastLookup(PropertyInfo[] properties, bool includeType)
{
int fastAccessIndex = includeType ? 1 : 0;
FastPropertyLookup[] fastLookup = new FastPropertyLookup[properties.Length + fastAccessIndex];
if (includeType)
{
fastLookup[0] = new FastPropertyLookup("Type", TypeCode.String, (o, p) => o.GetType().ToString());
}
foreach (var prop in properties)
{
var getterMethod = prop.GetGetMethod();
var propertyType = getterMethod?.ReturnType;
if (getterMethod is null || propertyType is null)
{
// Should never happen due to IsValidPublicProperty check
fastLookup[fastAccessIndex++] = new FastPropertyLookup(prop.Name, TypeCode.String, (o, p) => null);
}
else
{
ReflectionHelpers.LateBoundMethod valueLookup = ReflectionHelpers.CreateLateBoundMethod(getterMethod);
TypeCode typeCode = Type.GetTypeCode(propertyType); // Skip cyclic-reference checks when not TypeCode.Object
fastLookup[fastAccessIndex++] = new FastPropertyLookup(prop.Name, typeCode, valueLookup);
}
}
return fastLookup;
}
internal
#if !NETFRAMEWORK
readonly
#endif
struct ObjectPropertyList : IEnumerable<ObjectPropertyList.PropertyValue>
{
internal static readonly StringComparer NameComparer = StringComparer.Ordinal;
private static readonly FastPropertyLookup[] CreateIDictionaryEnumerator = new[] { new FastPropertyLookup(string.Empty, TypeCode.Object, (o, p) => ((IDictionary<string, object>)o).GetEnumerator()) };
private readonly object _object;
private readonly PropertyInfo[] _properties;
private readonly FastPropertyLookup[]? _fastLookup;
public
#if !NETFRAMEWORK
readonly
#endif
struct PropertyValue
{
public readonly string Name;
public readonly object? Value;
public TypeCode TypeCode => Value is null ? TypeCode.Empty : _typecode;
private readonly TypeCode _typecode;
public bool HasNameAndValue => Name != null && Value != null;
public PropertyValue(string name, object value, TypeCode typeCode)
{
Name = name;
Value = value;
_typecode = typeCode;
}
public PropertyValue(object owner, PropertyInfo propertyInfo)
{
Name = propertyInfo.Name;
_typecode = TypeCode.Object;
try
{
Value = propertyInfo.GetValue(owner, null);
}
catch
{
Value = null;
}
}
public PropertyValue(object owner, FastPropertyLookup fastProperty)
{
Name = fastProperty.Name;
_typecode = fastProperty.TypeCode;
try
{
Value = fastProperty.ValueLookup(owner, ArrayHelper.Empty<object>());
}
catch
{
Value = null;
}
}
public override string ToString() => $"{Name}={Value}";
}
public bool IsSimpleValue => _properties.Length == 0 && (_fastLookup is null || _fastLookup.Length == 0);
public object ObjectValue => _object;
internal ObjectPropertyList(object value, PropertyInfo[] properties, FastPropertyLookup[]? fastLookup)
{
_object = value;
_properties = properties;
_fastLookup = fastLookup;
}
public ObjectPropertyList(IDictionary<string, object> value)
{
_object = value; // Expando objects
_properties = ArrayHelper.Empty<PropertyInfo>();
_fastLookup = CreateIDictionaryEnumerator;
}
// The expando enumerator is stored as a single nameless entry, which no real member can be
private static bool IsExpandoLookup(FastPropertyLookup[] fastLookup)
=> fastLookup.Length == 1 && fastLookup[0].Name.Length == 0;
public bool TryGetPropertyValue(string name, out PropertyValue propertyValue)
{
if (_properties.Length == 0 && (_fastLookup is null || _fastLookup.Length == 0 || IsExpandoLookup(_fastLookup)))
{
if (_object is IDictionary<string, object> expandoObject)
{
if (expandoObject.TryGetValue(name, out var objectValue))
{
propertyValue = new PropertyValue(name, objectValue, TypeCode.Object);
return true;
}
propertyValue = default(PropertyValue);
return false;
}
return TryListLookupPropertyValue(name, out propertyValue);
}
if (_fastLookup != null)
{
// Scans properties for name (Skips string-compare and value-lookup until finding match)
int nameHashCode = NameComparer.GetHashCode(name);
foreach (var fastProperty in _fastLookup)
{
if (fastProperty.NameHashCode == nameHashCode && NameComparer.Equals(fastProperty.Name, name))
{
propertyValue = new PropertyValue(_object, fastProperty);
return true;
}
}
propertyValue = default(PropertyValue);
return false;
}
return TrySlowLookupPropertyValue(name, out propertyValue);
}
/// <summary>
/// Scans properties for name (Skips property value lookup until finding match)
/// </summary>
private bool TrySlowLookupPropertyValue(string name, out PropertyValue propertyValue)
{
foreach (var propInfo in _properties)
{
if (NameComparer.Equals(propInfo.Name, name))
{
propertyValue = new PropertyValue(_object, propInfo);
return true;
}
}
propertyValue = default(PropertyValue);
return false;
}
/// <summary>
/// Scans properties for name
/// </summary>
private bool TryListLookupPropertyValue(string name, out PropertyValue propertyValue)
{
foreach (var item in this)
{
if (NameComparer.Equals(item.Name, name))
{
propertyValue = item;
return true;
}
}
propertyValue = default(PropertyValue);
return false;
}
public override string ToString()
{
return _object?.ToString() ?? "null";
}
public Enumerator GetEnumerator()
{
if (_properties.Length > 0 || _fastLookup is null || _fastLookup.Length == 0 || !IsExpandoLookup(_fastLookup))
return new Enumerator(_object, _properties, _fastLookup);
var expandoObject = _fastLookup[0].ValueLookup(_object, ArrayHelper.Empty<object>());
return expandoObject is null ? default(Enumerator) : new Enumerator((IEnumerator<KeyValuePair<string, object>>)expandoObject);
}
IEnumerator<PropertyValue> IEnumerable<PropertyValue>.GetEnumerator() => GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
public struct Enumerator : IEnumerator<PropertyValue>
{
private readonly object _owner;
private readonly PropertyInfo[]? _properties;
private readonly FastPropertyLookup[]? _fastLookup;
private readonly IEnumerator<KeyValuePair<string, object>>? _enumerator;
private int _index;
internal Enumerator(object owner, PropertyInfo[] properties, FastPropertyLookup[]? fastLookup)
{
_owner = owner;
_properties = properties;
_fastLookup = fastLookup;
_index = -1;
_enumerator = null;
}
internal Enumerator(IEnumerator<KeyValuePair<string, object>> enumerator)
{
_owner = enumerator;
_properties = null;
_fastLookup = null;
_index = 0;
_enumerator = enumerator;
}
public PropertyValue Current
{
get
{
try
{
if (_fastLookup != null)
return new PropertyValue(_owner, _fastLookup[_index]);
else if (_properties != null)
return new PropertyValue(_owner, _properties[_index]);
else if (_enumerator != null)
return new PropertyValue(_enumerator.Current.Key, _enumerator.Current.Value, TypeCode.Object);
else
return default(PropertyValue);
}
catch (Exception ex)
{
InternalLogger.Debug(ex, "Failed to get property value for object: {0}", _owner);
return default(PropertyValue);
}
}
}
object IEnumerator.Current => Current;
public void Dispose()
{
_enumerator?.Dispose();
}
public bool MoveNext()
{
if (_properties != null)
return ++_index < (_fastLookup?.Length ?? _properties.Length);
else
return _enumerator?.MoveNext() == true;
}
public void Reset()
{
if (_properties != null)
_index = -1;
else
_enumerator?.Reset();
}
}
}
internal
#if !NETFRAMEWORK
readonly
#endif
struct FastPropertyLookup
{
public readonly string Name;
public readonly ReflectionHelpers.LateBoundMethod ValueLookup;
public readonly TypeCode TypeCode;
public readonly int NameHashCode;
public FastPropertyLookup(string name, TypeCode typeCode, ReflectionHelpers.LateBoundMethod valueLookup)
{
Name = name;
ValueLookup = valueLookup;
TypeCode = typeCode;
NameHashCode = ObjectPropertyList.NameComparer.GetHashCode(name);
}
}
private
#if !NETFRAMEWORK
readonly
#endif
struct ObjectPropertyInfos : IEquatable<ObjectPropertyInfos>
{
public readonly PropertyInfo[] Properties;
public readonly FastPropertyLookup[]? FastLookup;
/// <summary>Public fields of a type without public properties, null until resolved</summary>
public readonly FastPropertyLookup[]? FieldLookup;
public static readonly ObjectPropertyInfos SimpleToString = new ObjectPropertyInfos(ArrayHelper.Empty<PropertyInfo>(), ArrayHelper.Empty<FastPropertyLookup>());
public ObjectPropertyInfos(PropertyInfo[] properties, FastPropertyLookup[]? fastLookup, FastPropertyLookup[]? fieldLookup = null)
{
Properties = properties;
FastLookup = fastLookup;
FieldLookup = fieldLookup;
}
public bool HasFastLookup => FastLookup != null;
public bool NeedsFieldLookup => FieldLookup is null && Properties.Length == 0 && FastLookup?.Length == 0;
public ObjectPropertyList CreatePropertyList(object value, bool includeFields)
{
if (includeFields && FieldLookup?.Length > 0)
return new ObjectPropertyList(value, Properties, FieldLookup);
return new ObjectPropertyList(value, Properties, FastLookup);
}
public bool Equals(ObjectPropertyInfos other)
{
return ReferenceEquals(Properties, other.Properties) && FastLookup?.Length == other.FastLookup?.Length && ReferenceEquals(FieldLookup, other.FieldLookup);
}
}
#if !NET35 && !NET40 && NETFRAMEWORK
private static Dictionary<string, object> DynamicObjectToDict(System.Dynamic.DynamicObject d)
{
var newVal = new Dictionary<string, object>();
foreach (var propName in d.GetDynamicMemberNames())
{
if (d.TryGetMember(new GetBinderAdapter(propName), out var result))
{
newVal[propName] = result;
}
}
return newVal;
}
/// <summary>
/// Binder for retrieving value of <see cref="System.Dynamic.DynamicObject"/>
/// </summary>
private sealed class GetBinderAdapter : System.Dynamic.GetMemberBinder
{
internal GetBinderAdapter(string name)
: base(name, false)
{
}
/// <inheritdoc/>
public override System.Dynamic.DynamicMetaObject FallbackGetMember(System.Dynamic.DynamicMetaObject target, System.Dynamic.DynamicMetaObject errorSuggestion)
{
return target;
}
}
#endif
private static IDictionaryEnumerator? TryGetDictionaryEnumerator(object value)
{
if (!(value is IEnumerable) || value is string)
return null;
if (value is IDictionary<string, object>)
return new DictionaryEnumerator<string, object>();
if (value is IDictionary<string, string>)
return new DictionaryEnumerator<string, string>();
#if !NET35 && !NET40
if (value is IReadOnlyDictionary<string, object>)
return new DictionaryEnumerator<string, object>();
if (value is IReadOnlyDictionary<string, string>)
return new DictionaryEnumerator<string, string>();
#endif
if (value is IDictionary && value.GetType().IsGenericType)
{
if (value.GetType().GetGenericArguments()[0] == typeof(string))
return new DictionaryEnumerator(DictionaryEnumerator.BuildDictionaryEnumerator);
else
return null;
}
return TryBuildDictionaryEnumerator(value);
}
[UnconditionalSuppressMessage("Trimming - Allow reflection of message args", "IL2075")]
private static IDictionaryEnumerator? TryBuildDictionaryEnumerator(object value)
{
foreach (var interfaceType in value.GetType().GetInterfaces())
{
if (interfaceType.IsGenericType
&& (interfaceType.GetGenericTypeDefinition() == typeof(IDictionary<,>)
#if !NET35 && !NET40
|| interfaceType.GetGenericTypeDefinition() == typeof(IReadOnlyDictionary<,>)
#endif
)
&& interfaceType.GetGenericArguments()[0] == typeof(string))
{
#if NETFRAMEWORK
return (IDictionaryEnumerator)Activator.CreateInstance(typeof(DictionaryEnumerator<,>).MakeGenericType(interfaceType.GetGenericArguments()));
#else
// AOT does not support creating new types at runtime
foreach (var subInterfaceType in value.GetType().GetInterfaces())
{
if (subInterfaceType.IsGenericType
&& subInterfaceType.GetGenericTypeDefinition() == typeof(IEnumerable<>)
&& subInterfaceType.GetGenericArguments()[0].IsGenericType
&& subInterfaceType.GetGenericArguments()[0].GetGenericTypeDefinition() == typeof(KeyValuePair<,>)
&& subInterfaceType.GetGenericArguments()[0].GetGenericArguments()[0] == typeof(string))
{
var enumerator = DictionaryEnumerator.BuildEnumerator(subInterfaceType, subInterfaceType.GetGenericArguments()[0]);
if (enumerator != null)
return new DictionaryEnumerator(enumerator);
}
}
#endif
}
}
return null;
}
private interface IDictionaryEnumerator
{
IEnumerator<KeyValuePair<string, object?>> GetEnumerator(object value);
}
private sealed class DictionaryEnumerator : IDictionaryEnumerator
{
private readonly Func<object, IEnumerator<KeyValuePair<string, object?>>> _enumerateCollection;
public DictionaryEnumerator(Func<object, IEnumerator<KeyValuePair<string, object?>>> enumerateCollection)
{
_enumerateCollection = enumerateCollection;
}
IEnumerator<KeyValuePair<string, object?>> IDictionaryEnumerator.GetEnumerator(object value)
{
return _enumerateCollection(value);
}
public static IEnumerator<KeyValuePair<string, object?>> BuildDictionaryEnumerator(object collection)
{
if (collection is IDictionary dictionary)
{
if (dictionary.Count == 0)
return EmptyDictionaryEnumerator.Default;
return YieldEnumerator(dictionary);
}
return EmptyDictionaryEnumerator.Default;
}
private static IEnumerator<KeyValuePair<string, object?>> YieldEnumerator(IDictionary dictionary)
{
foreach (var item in new DictionaryEntryEnumerable(dictionary))
yield return new KeyValuePair<string, object?>(item.Key?.ToString() ?? string.Empty, item.Value);
}
#if !NETFRAMEWORK
[UnconditionalSuppressMessage("Trimming - Allow reflection of message args", "IL2075")]
[UnconditionalSuppressMessage("Trimming - Allow reflection of message args", "IL2070")]
public static Func<object, IEnumerator<KeyValuePair<string, object?>>>? BuildEnumerator(Type dictionaryType, Type dictionaryItemType)
{
var enumeratorMethod = dictionaryType.GetMethod(nameof(IEnumerable.GetEnumerator));
var getKeyMethod = dictionaryItemType.GetProperty(nameof(KeyValuePair<string, object>.Key))?.GetGetMethod();
var getValueMethod = dictionaryItemType.GetProperty(nameof(KeyValuePair<string, object>.Value))?.GetGetMethod();
if (enumeratorMethod != null && getKeyMethod != null && getValueMethod != null)
{
var enumeratorGetter = ReflectionHelpers.CreateLateBoundMethod(enumeratorMethod);
var keyGetter = ReflectionHelpers.CreateLateBoundMethod(getKeyMethod);
var valueGetter = ReflectionHelpers.CreateLateBoundMethod(getValueMethod);
return (collection) =>
{
if (collection is IEnumerable && dictionaryType.IsInstanceOfType(collection))
{
var enumerator = (IEnumerator?)enumeratorGetter.Invoke(collection, ArrayHelper.Empty<object>());
if (enumerator is null || !enumerator.MoveNext())
{
(enumerator as IDisposable)?.Dispose();
return EmptyDictionaryEnumerator.Default;
}
return EnumerateItems(enumerator, keyGetter, valueGetter);
}
return EmptyDictionaryEnumerator.Default;
};
}
return null;
}
private static IEnumerator<KeyValuePair<string, object?>> EnumerateItems(IEnumerator enumerator, ReflectionHelpers.LateBoundMethod getKeyProperty, ReflectionHelpers.LateBoundMethod getValueProperty)
{
try
{
do
{
var item = enumerator.Current;
var keyString = getKeyProperty.Invoke(item, ArrayHelper.Empty<object>());
var valueObject = getValueProperty.Invoke(item, ArrayHelper.Empty<object>());
yield return new KeyValuePair<string, object?>(keyString?.ToString() ?? string.Empty, valueObject);
} while (enumerator.MoveNext());
}
finally
{
(enumerator as IDisposable)?.Dispose();
}
}
#endif
}
private sealed class DictionaryEnumerator<TKey, TValue> : IDictionaryEnumerator
{
public IEnumerator<KeyValuePair<string, object?>> GetEnumerator(object value)
{
if (value is IDictionary<TKey, TValue> dictionary)
{
if (dictionary.Count > 0)
return YieldEnumerator(dictionary);
}
#if !NET35 && !NET40
else if (value is IReadOnlyDictionary<TKey, TValue> readonlyDictionary)
{
if (readonlyDictionary.Count > 0)
return YieldEnumerator(readonlyDictionary);
}
#endif
return EmptyDictionaryEnumerator.Default;
}
private static IEnumerator<KeyValuePair<string, object?>> YieldEnumerator(IDictionary<TKey, TValue> dictionary)
{
foreach (var item in dictionary)
yield return new KeyValuePair<string, object?>(item.Key?.ToString() ?? string.Empty, item.Value);
}
#if !NET35 && !NET40
private static IEnumerator<KeyValuePair<string, object?>> YieldEnumerator(IReadOnlyDictionary<TKey, TValue> dictionary)
{
foreach (var item in dictionary)
yield return new KeyValuePair<string, object?>(item.Key?.ToString() ?? string.Empty, item.Value);
}
#endif
}
private sealed class EmptyDictionaryEnumerator : IEnumerator<KeyValuePair<string, object?>>
{
public static readonly EmptyDictionaryEnumerator Default = new EmptyDictionaryEnumerator();
KeyValuePair<string, object?> IEnumerator<KeyValuePair<string, object?>>.Current => default(KeyValuePair<string, object?>);
object IEnumerator.Current => default(KeyValuePair<string, object?>);
bool IEnumerator.MoveNext() => false;
void IDisposable.Dispose()
{
// Nothing here on purpose
}
void IEnumerator.Reset()
{
// Nothing here on purpose
}
}
}
}