/
githubmirror
/
aspnetcore
Обзор
Документация
Войти
/
githubmirror
/
aspnetcore
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/Shared/PropertyHelper/PropertyHelper.cs
568 строк
23 KB
Andrew Casey
Remove unnecessary pragmas (#55810)
29 май 2024, 01:20
Не верифицирован
29 май 2024, 01:20
f8c9806
Код
Авторство
О чём код?
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. #nullable enable using System.Collections.Concurrent; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Reflection; using System.Reflection.Metadata; using System.Runtime.CompilerServices; [assembly: MetadataUpdateHandler(typeof(Microsoft.Extensions.Internal.PropertyHelper.MetadataUpdateHandler))] namespace Microsoft.Extensions.Internal; internal sealed class PropertyHelper { private const BindingFlags DeclaredOnlyLookup = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly; private const BindingFlags Everything = BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance; // Delegate type for a by-ref property getter private delegate TValue ByRefFunc<TDeclaringType, TValue>(ref TDeclaringType arg); private static readonly MethodInfo CallPropertyGetterOpenGenericMethod = typeof(PropertyHelper).GetMethod(nameof(CallPropertyGetter), DeclaredOnlyLookup)!; private static readonly MethodInfo CallPropertyGetterByReferenceOpenGenericMethod = typeof(PropertyHelper).GetMethod(nameof(CallPropertyGetterByReference), DeclaredOnlyLookup)!; private static readonly MethodInfo CallNullSafePropertyGetterOpenGenericMethod = typeof(PropertyHelper).GetMethod(nameof(CallNullSafePropertyGetter), DeclaredOnlyLookup)!; private static readonly MethodInfo CallNullSafePropertyGetterByReferenceOpenGenericMethod = typeof(PropertyHelper).GetMethod(nameof(CallNullSafePropertyGetterByReference), DeclaredOnlyLookup)!; private static readonly MethodInfo CallPropertySetterOpenGenericMethod = typeof(PropertyHelper).GetMethod(nameof(CallPropertySetter), DeclaredOnlyLookup)!; // Using an array rather than IEnumerable, as target will be called on the hot path numerous times. private static readonly ConcurrentDictionary<Type, PropertyHelper[]> PropertiesCache = new(); private static readonly ConcurrentDictionary<Type, PropertyHelper[]> VisiblePropertiesCache = new(); private Action<object, object?>? _valueSetter; private Func<object, object?>? _valueGetter; /// <summary> /// Initializes a fast <see cref="PropertyHelper"/>. /// This constructor does not cache the helper. For caching, use <see cref="GetProperties(Type)"/>. /// </summary> public PropertyHelper(PropertyInfo property) { Property = property ?? throw new ArgumentNullException(nameof(property)); Name = property.Name; } /// <summary> /// Gets the backing <see cref="PropertyInfo"/>. /// </summary> public PropertyInfo Property { get; } /// <summary> /// Gets (or sets in derived types) the property name. /// </summary> public string Name { get; } /// <summary> /// Gets the property value getter. /// </summary> public Func<object, object?> ValueGetter { [RequiresUnreferencedCode("This API is not trim safe.")] get { return _valueGetter ??= MakeFastPropertyGetter(Property); } } /// <summary> /// Gets the property value setter. /// </summary> public Action<object, object?> ValueSetter { [RequiresUnreferencedCode("This API is not trim safe.")] get { return _valueSetter ??= MakeFastPropertySetter(Property); } } /// <summary> /// Returns the property value for the specified <paramref name="instance"/>. /// </summary> /// <param name="instance">The object whose property value will be returned.</param> /// <returns>The property value.</returns> [RequiresUnreferencedCode("This API is not trim safe.")] public object? GetValue(object instance) { return ValueGetter(instance); } /// <summary> /// Sets the property value for the specified <paramref name="instance" />. /// </summary> /// <param name="instance">The object whose property value will be set.</param> /// <param name="value">The property value.</param> [RequiresUnreferencedCode("This API is not trim safe.")] public void SetValue(object instance, object? value) { ValueSetter(instance, value); } /// <summary> /// Creates and caches fast property helpers that expose getters for every public get property on the /// specified type. /// </summary> /// <param name="type">The type to extract property accessors for.</param> /// <returns>A cached array of all public properties of the specified type. /// </returns> [RequiresUnreferencedCode("This API is not trim safe.")] public static PropertyHelper[] GetProperties( [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties)] Type type) { return GetProperties(type, PropertiesCache); } /// <summary> /// <para> /// Creates and caches fast property helpers that expose getters for every non-hidden get property /// on the specified type. /// </para> /// <para> /// <see cref="M:GetVisibleProperties"/> excludes properties defined on base types that have been /// hidden by definitions using the <c>new</c> keyword. /// </para> /// </summary> /// <param name="type">The type to extract property accessors for.</param> /// <returns> /// A cached array of all public properties of the specified type. /// </returns> [RequiresUnreferencedCode("This API is not trim safe.")] public static PropertyHelper[] GetVisibleProperties( [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties)] Type type) { return GetVisibleProperties(type, PropertiesCache, VisiblePropertiesCache); } /// <summary> /// Creates a single fast property getter. The result is not cached. /// </summary> /// <param name="propertyInfo">propertyInfo to extract the getter for.</param> /// <returns>a fast getter.</returns> /// <remarks> /// This method is more memory efficient than a dynamically compiled lambda, and about the /// same speed. /// </remarks> [RequiresUnreferencedCode("This API is not trimmer safe.")] public static Func<object, object?> MakeFastPropertyGetter(PropertyInfo propertyInfo) { Debug.Assert(propertyInfo != null); return MakeFastPropertyGetter( propertyInfo, CallPropertyGetterOpenGenericMethod, CallPropertyGetterByReferenceOpenGenericMethod); } /// <summary> /// Creates a single fast property getter which is safe for a null input object. The result is not cached. /// </summary> /// <param name="propertyInfo">propertyInfo to extract the getter for.</param> /// <returns>a fast getter.</returns> /// <remarks> /// This method is more memory efficient than a dynamically compiled lambda, and about the /// same speed. /// </remarks> [RequiresUnreferencedCode("This API is not trimmer safe.")] public static Func<object, object?> MakeNullSafeFastPropertyGetter(PropertyInfo propertyInfo) { Debug.Assert(propertyInfo != null); return MakeFastPropertyGetter( propertyInfo, CallNullSafePropertyGetterOpenGenericMethod, CallNullSafePropertyGetterByReferenceOpenGenericMethod); } [RequiresUnreferencedCode("This API is not trimmer safe.")] private static Func<object, object?> MakeFastPropertyGetter( PropertyInfo propertyInfo, MethodInfo propertyGetterWrapperMethod, MethodInfo propertyGetterByRefWrapperMethod) { Debug.Assert(propertyInfo != null); // Must be a generic method with a Func<,> parameter Debug.Assert(propertyGetterWrapperMethod != null); Debug.Assert(propertyGetterWrapperMethod.IsGenericMethodDefinition); Debug.Assert(propertyGetterWrapperMethod.GetParameters().Length == 2); // Must be a generic method with a ByRefFunc<,> parameter Debug.Assert(propertyGetterByRefWrapperMethod != null); Debug.Assert(propertyGetterByRefWrapperMethod.IsGenericMethodDefinition); Debug.Assert(propertyGetterByRefWrapperMethod.GetParameters().Length == 2); var getMethod = propertyInfo.GetMethod; Debug.Assert(getMethod != null); Debug.Assert(!getMethod.IsStatic); Debug.Assert(getMethod.GetParameters().Length == 0); // MakeGenericMethod + value type requires IsDynamicCodeSupported to be true. if (RuntimeFeature.IsDynamicCodeSupported) { // Instance methods in the CLR can be turned into static methods where the first parameter // is open over "target". This parameter is always passed by reference, so we have a code // path for value types and a code path for reference types. if (getMethod.DeclaringType!.IsValueType) { // Create a delegate (ref TDeclaringType) -> TValue return MakeFastPropertyGetter( typeof(ByRefFunc<,>), getMethod, propertyGetterByRefWrapperMethod); } else { // Create a delegate TDeclaringType -> TValue return MakeFastPropertyGetter( typeof(Func<,>), getMethod, propertyGetterWrapperMethod); } } else { return propertyInfo.GetValue; } } [RequiresUnreferencedCode("This API is not trimmer safe.")] [RequiresDynamicCode("This API requires dynamic code because it makes generic types which may be filled with ValueTypes.")] private static Func<object, object?> MakeFastPropertyGetter( Type openGenericDelegateType, MethodInfo propertyGetMethod, MethodInfo openGenericWrapperMethod) { var typeInput = propertyGetMethod.DeclaringType!; var typeOutput = propertyGetMethod.ReturnType; var delegateType = openGenericDelegateType.MakeGenericType(typeInput, typeOutput); var propertyGetterDelegate = propertyGetMethod.CreateDelegate(delegateType); var wrapperDelegateMethod = openGenericWrapperMethod.MakeGenericMethod(typeInput, typeOutput); var accessorDelegate = wrapperDelegateMethod.CreateDelegate( typeof(Func<object, object?>), propertyGetterDelegate); return (Func<object, object?>)accessorDelegate; } /// <summary> /// Creates a single fast property setter for reference types. The result is not cached. /// </summary> /// <param name="propertyInfo">propertyInfo to extract the setter for.</param> /// <returns>a fast getter.</returns> /// <remarks> /// This method is more memory efficient than a dynamically compiled lambda, and about the /// same speed. This only works for reference types. /// </remarks> [RequiresUnreferencedCode("This API is not trimmer safe.")] public static Action<object, object?> MakeFastPropertySetter(PropertyInfo propertyInfo) { Debug.Assert(propertyInfo != null); Debug.Assert(!propertyInfo.DeclaringType!.IsValueType); var setMethod = propertyInfo.SetMethod; Debug.Assert(setMethod != null); Debug.Assert(!setMethod.IsStatic); Debug.Assert(setMethod.ReturnType == typeof(void)); var parameters = setMethod.GetParameters(); Debug.Assert(parameters.Length == 1); // MakeGenericMethod + value type requires IsDynamicCodeSupported to be true. if (RuntimeFeature.IsDynamicCodeSupported) { // Instance methods in the CLR can be turned into static methods where the first parameter // is open over "target". This parameter is always passed by reference, so we have a code // path for value types and a code path for reference types. var typeInput = setMethod.DeclaringType!; var parameterType = parameters[0].ParameterType; // Create a delegate TDeclaringType -> { TDeclaringType.Property = TValue; } var propertySetterAsAction = setMethod.CreateDelegate(typeof(Action<,>).MakeGenericType(typeInput, parameterType)); var callPropertySetterClosedGenericMethod = CallPropertySetterOpenGenericMethod.MakeGenericMethod(typeInput, parameterType); var callPropertySetterDelegate = callPropertySetterClosedGenericMethod.CreateDelegate( typeof(Action<object, object?>), propertySetterAsAction); return (Action<object, object?>)callPropertySetterDelegate; } else { return propertyInfo.SetValue; } } /// <summary> /// Given an object, adds each instance property with a public get method as a key and its /// associated value to a dictionary. /// /// If the object is already an <see cref="IDictionary{String, Object}"/> instance, then a copy /// is returned. /// </summary> /// <remarks> /// The implementation of PropertyHelper will cache the property accessors per-type. This is /// faster when the same type is used multiple times with ObjectToDictionary. /// </remarks> [RequiresUnreferencedCode("Method uses reflection to generate the dictionary.")] public static IDictionary<string, object?> ObjectToDictionary(object? value) { if (value is IDictionary<string, object?> dictionary) { return new Dictionary<string, object?>(dictionary, StringComparer.OrdinalIgnoreCase); } dictionary = new Dictionary<string, object?>(StringComparer.OrdinalIgnoreCase); if (value is not null) { foreach (var helper in GetProperties(value.GetType(), PropertiesCache)) { dictionary[helper.Name] = helper.GetValue(value); } } return dictionary; } // Called via reflection private static object? CallPropertyGetter<TDeclaringType, TValue>( Func<TDeclaringType, TValue> getter, object target) { return getter((TDeclaringType)target); } // Called via reflection private static object? CallPropertyGetterByReference<TDeclaringType, TValue>( ByRefFunc<TDeclaringType, TValue> getter, object target) { var unboxed = (TDeclaringType)target; return getter(ref unboxed); } // Called via reflection private static object? CallNullSafePropertyGetter<TDeclaringType, TValue>( Func<TDeclaringType, TValue> getter, object target) { if (target == null) { return null; } return getter((TDeclaringType)target); } // Called via reflection private static object? CallNullSafePropertyGetterByReference<TDeclaringType, TValue>( ByRefFunc<TDeclaringType, TValue> getter, object target) { if (target == null) { return null; } var unboxed = (TDeclaringType)target; return getter(ref unboxed); } private static void CallPropertySetter<TDeclaringType, TValue>( Action<TDeclaringType, TValue> setter, object target, object value) { setter((TDeclaringType)target, (TValue)value); } /// <summary> /// <para> /// Creates and caches fast property helpers that expose getters for every non-hidden get property /// on the specified type. /// </para> /// <para> /// <see cref="M:GetVisibleProperties"/> excludes properties defined on base types that have been /// hidden by definitions using the <c>new</c> keyword. /// </para> /// </summary> /// <param name="type">The type to extract property accessors for.</param> /// <param name="allPropertiesCache">The cache to store results in. Use <see cref="PropertiesCache"/> to use the default cache. Use <see langword="null"/> to avoid caching.</param> /// <param name="visiblePropertiesCache">The cache to store results in. Use <see cref="VisiblePropertiesCache"/> if the calling type does not have its own independent cache. Use <see langword="null"/> to avoid caching.</param> /// <returns> /// A cached array of all public properties of the specified type. /// </returns> [RequiresUnreferencedCode("This API is not trim safe.")] public static PropertyHelper[] GetVisibleProperties( Type type, ConcurrentDictionary<Type, PropertyHelper[]>? allPropertiesCache, ConcurrentDictionary<Type, PropertyHelper[]>? visiblePropertiesCache) { if (visiblePropertiesCache is not null && visiblePropertiesCache.TryGetValue(type, out var result)) { return result; } // The simple and common case, this is normal POCO object - no need to allocate. var allPropertiesDefinedOnType = true; var allProperties = GetProperties(type, allPropertiesCache); foreach (var propertyHelper in allProperties) { if (propertyHelper.Property.DeclaringType != type) { allPropertiesDefinedOnType = false; break; } } if (allPropertiesDefinedOnType) { result = allProperties; visiblePropertiesCache?.TryAdd(type, result); return result; } // There's some inherited properties here, so we need to check for hiding via 'new'. var filteredProperties = new List<PropertyHelper>(allProperties.Length); foreach (var propertyHelper in allProperties) { var declaringType = propertyHelper.Property.DeclaringType; if (declaringType == type) { filteredProperties.Add(propertyHelper); continue; } // If this property was declared on a base type then look for the definition closest to the // the type to see if we should include it. var ignoreProperty = false; // Walk up the hierarchy until we find the type that actually declares this // PropertyInfo. Type? currentType = type; while (currentType != null && currentType != declaringType) { // We've found a 'more proximal' public definition var declaredProperty = currentType.GetProperty(propertyHelper.Name, DeclaredOnlyLookup); if (declaredProperty != null) { ignoreProperty = true; break; } currentType = currentType.BaseType; } if (!ignoreProperty) { filteredProperties.Add(propertyHelper); } } result = filteredProperties.ToArray(); visiblePropertiesCache?.TryAdd(type, result); return result; } /// <summary> /// Creates and caches fast property helpers that expose getters for every public get property on the /// specified type. /// </summary> /// <param name="type">The type to extract property accessors for.</param> /// <param name="cache">The cache to store results in. Use <see cref="PropertiesCache"/> to use the default cache. Use <see langword="null"/> to avoid caching.</param> /// <returns>A cached array of all public properties of the specified type. /// </returns> // There isn't a way to represent trimmability requirements since for type since we unwrap nullable types. [RequiresUnreferencedCode("This API is not trim safe.")] public static PropertyHelper[] GetProperties( Type type, ConcurrentDictionary<Type, PropertyHelper[]>? cache) { // Unwrap nullable types. This means Nullable<T>.Value and Nullable<T>.HasValue will not be // part of the sequence of properties returned by this method. type = Nullable.GetUnderlyingType(type) ?? type; if (cache is null || !cache.TryGetValue(type, out var result)) { var propertyHelpers = new List<PropertyHelper>(); // We avoid loading indexed properties using the Where statement. AddInterestingProperties(propertyHelpers, type); if (type.IsInterface) { // Reflection does not return information about inherited properties on the interface itself. foreach (var @interface in type.GetInterfaces()) { AddInterestingProperties(propertyHelpers, @interface); } } result = propertyHelpers.ToArray(); cache?.TryAdd(type, result); } return result; static void AddInterestingProperties( List<PropertyHelper> propertyHelpers, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties)] Type type) { foreach (var property in type.GetProperties(Everything)) { if (!IsInterestingProperty(property)) { continue; } propertyHelpers.Add(new PropertyHelper(property)); } } } private static bool IsInterestingProperty(PropertyInfo property) { // For improving application startup time, do not use GetIndexParameters() api early in this check as it // creates a copy of parameter array and also we would like to check for the presence of a get method // and short circuit asap. return property.GetMethod != null && property.GetMethod.IsPublic && !property.GetMethod.IsStatic && // PropertyHelper can't really interact with ref-struct properties since they can't be // boxed and can't be used as generic types. We just ignore them. // // see: https://github.com/aspnet/Mvc/issues/8545 !property.PropertyType.IsByRefLike && // Indexed properties are not useful (or valid) for grabbing properties off an object. property.GetMethod.GetParameters().Length == 0; } internal static class MetadataUpdateHandler { /// <summary> /// Invoked as part of <see cref="MetadataUpdateHandlerAttribute" /> contract for hot reload. /// </summary> public static void ClearCache(Type[]? _) { PropertiesCache.Clear(); VisiblePropertiesCache.Clear(); } } }