895 lines
37 KiB
C#
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
|
|
}
|
|
}
|
|
}
|
|
}
|