< Summary

Information
Class: MyNet.Reflection.ReflectionExtensions
Assembly: MyNet.Reflection
File(s): https://raw.githubusercontent.com/sandre58/MyNet/85372080fe102cd9ee155ceab49ae000e7f66103/src/MyNet.Reflection/Extensions/ReflectionExtensions.cs
Tag: 323_28699572109
Line coverage
86%
Covered lines: 56
Uncovered lines: 9
Coverable lines: 65
Total lines: 287
Line coverage: 86.1%
Branch coverage
65%
Covered branches: 26
Total branches: 40
Branch coverage: 65%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

File(s)

https://raw.githubusercontent.com/sandre58/MyNet/85372080fe102cd9ee155ceab49ae000e7f66103/src/MyNet.Reflection/Extensions/ReflectionExtensions.cs

#LineLine coverage
 1// -----------------------------------------------------------------------
 2// <copyright file="ReflectionExtensions.cs" company="Stéphane ANDRE">
 3// Copyright (c) Stéphane ANDRE. All rights reserved.
 4// </copyright>
 5// -----------------------------------------------------------------------
 6
 7using System;
 8using System.Collections.Concurrent;
 9using System.Collections.Generic;
 10using System.Linq;
 11using System.Reflection;
 12using MyNet.Primitives.Metadata;
 13
 14#pragma warning disable IDE0130 // Namespace does not match folder structure
 15namespace MyNet.Reflection;
 16#pragma warning restore IDE0130 // Namespace does not match folder structure
 17
 18public static class ReflectionExtensions
 19{
 20    extension(MemberInfo memberInfo)
 21    {
 22        /// <summary>
 23        /// Gets the attribute of the specified type applied to the member.
 24        /// </summary>
 25        /// <typeparam name="TAttribute">The attribute type.</typeparam>
 26        /// <param name="inherit">
 27        /// Indicates whether to search the member's inheritance chain.
 28        /// </param>
 29        /// <returns>
 30        /// The attribute instance if found; otherwise <c>null</c>.
 31        /// </returns>
 32        public TAttribute? GetAttribute<TAttribute>(bool inherit = true)
 33            where TAttribute : Attribute
 10534            => memberInfo.GetCustomAttribute<TAttribute>(inherit);
 35    }
 36
 37    /// <summary>
 38    /// Gets the public instance properties of the specified type, using a cache to improve performance on repeated call
 39    /// </summary>
 40    extension(Type type)
 41    {
 42        /// <summary>
 43        /// Gets the public instance properties of the specified type, using a cache to improve performance on repeated 
 44        /// </summary>
 58245        public PropertyInfo[] GetPublicProperties() => [.. GetCachedPublicProperties(type)];
 46
 47        /// <summary>
 48        /// Gets the public instance properties of the specified type that are decorated with the specified attribute, u
 49        /// </summary>
 50        public PropertyInfo[] GetPublicPropertiesWithAttribute<TAttribute>()
 51            where TAttribute : Attribute
 652            => [.. GetCachedPublicPropertiesWithAttribute(type, typeof(TAttribute))];
 53
 54        /// <summary>
 55        /// Gets a dictionary mapping property names to their corresponding PropertyInfo objects for the specified type,
 56        /// </summary>
 657        public Dictionary<string, PropertyInfo> GetPropertyMap() => new(GetCachedPropertyMap(type), StringComparer.Ordin
 58
 59        /// <summary>
 60        /// Gets the default value for the specified type. For reference types, this will be null; for value types, this
 61        /// </summary>
 62        /// <returns>The default value for the specified type.</returns>
 063        public object? GetDefault() => type.IsValueType ? Activator.CreateInstance(type) : null;
 64
 65        /// <summary>
 66        /// Determines whether the specified type is a simple type. Simple types include primitive types, enums, strings
 67        /// </summary>
 68        /// <returns>True if the type is a simple type; otherwise, false.</returns>
 69        public bool IsSimple()
 70        {
 11471            type = Nullable.GetUnderlyingType(type) ?? type;
 72
 11473            return
 11474                type.IsPrimitive ||
 11475                type.IsEnum ||
 11476                type == typeof(string) ||
 11477                type == typeof(decimal) ||
 11478                type == typeof(DateTime) ||
 11479                type == typeof(DateTimeOffset) ||
 11480                type == typeof(TimeSpan) ||
 11481                type == typeof(Guid) ||
 11482                type == typeof(Uri);
 83        }
 84
 85        /// <summary>
 86        /// Gets the attribute of the specified type applied to the type.
 87        /// </summary>
 88        /// <typeparam name="TAttribute">The attribute type.</typeparam>
 89        /// <param name="inherit">
 90        /// Indicates whether to search the inheritance chain.
 91        /// </param>
 92        /// <returns>
 93        /// The attribute instance if found; otherwise <c>null</c>.
 94        /// </returns>
 95        public TAttribute? GetAttribute<TAttribute>(bool inherit = true)
 96            where TAttribute : Attribute
 097            => type.GetCustomAttribute<TAttribute>(inherit);
 98    }
 99
 100    /// <summary>
 101    /// Extension methods for PropertyInfo to check for attributes and accessors.
 102    /// </summary>
 103    extension(PropertyInfo property)
 104    {
 105        /// <summary>
 106        /// Determines whether the property is decorated with the specified attribute, checking the inheritance chain if
 107        /// </summary>
 108        /// <typeparam name="TAttribute">The type of the attribute to check for.</typeparam>
 109        /// <returns>True if the property is decorated with the specified attribute; otherwise, false.</returns>
 110        public bool HasAttribute<TAttribute>()
 111            where TAttribute : Attribute
 0112            => property.IsDefined(typeof(TAttribute), inherit: true);
 113
 114        /// <summary>
 115        /// Determines whether the property has a public getter or setter.
 116        /// </summary>
 117        /// <returns>True if the property has a public getter or setter; otherwise, false.</returns>
 118        public bool HasPublicGetterOrSetter()
 0119            => property.GetMethod?.IsPublic == true ||
 0120               property.SetMethod?.IsPublic == true;
 121
 122        /// <summary>
 123        /// Gets the attribute of the specified type applied to the property.
 124        /// </summary>
 125        /// <typeparam name="TAttribute">The attribute type.</typeparam>
 126        /// <param name="inherit">
 127        /// Indicates whether to search the inheritance chain.
 128        /// </param>
 129        /// <returns>
 130        /// The attribute instance if found; otherwise <c>null</c>.
 131        /// </returns>
 132        public TAttribute? GetAttribute<TAttribute>(bool inherit = true)
 133            where TAttribute : Attribute
 0134            => property.GetCustomAttribute<TAttribute>(inherit);
 135
 136        /// <summary>
 137        /// Gets the symbol associated with the enum value by retrieving the SymbolAttribute applied to it. Returns null
 138        /// </summary>
 139        /// <returns>The symbol associated with the enum value, or null if the SymbolAttribute is not found.</returns>
 0140        public string? GetSymbol() => property.GetAttribute<SymbolAttribute>()?.Value;
 141    }
 142
 143    extension(Enum value)
 144    {
 145        /// <summary>
 146        /// Gets the specified attribute from an enum value, checking the inheritance chain if necessary. Returns null i
 147        /// </summary>
 148        /// <typeparam name="TAttribute">The type of the attribute to get.</typeparam>
 149        /// <returns>The attribute of the specified type, or null if not found.</returns>
 150        public TAttribute? GetAttribute<TAttribute>()
 151            where TAttribute : Attribute
 152        {
 111153            var member = value.GetType().GetField(value.ToString());
 154
 111155            return member?.GetAttribute<TAttribute>();
 156        }
 157
 158        /// <summary>
 159        /// Gets the symbol associated with the enum value by retrieving the SymbolAttribute applied to it. Returns null
 160        /// </summary>
 161        /// <returns>The symbol associated with the enum value, or null if the SymbolAttribute is not found.</returns>
 0162        public string? GetSymbol() => value.GetAttribute<SymbolAttribute>()?.Value;
 163    }
 164
 165    /// <summary>
 166    /// Gets the value of a nested property specified by a dot-separated path, using reflection to traverse the object g
 167    /// </summary>
 168    extension(object root)
 169    {
 170        /// <summary>
 171        /// Gets the value of a nested property specified by a dot-separated path, using reflection to traverse the obje
 172        /// </summary>
 173        /// <param name="path">The dot-separated path of the nested property.</param>
 174        /// <returns>The value of the nested property, or null if any part of the path is invalid or if the final value 
 175        public object? GetDeepPropertyValue(string path)
 176        {
 18177            if (string.IsNullOrWhiteSpace(path))
 0178                return null;
 179
 18180            var parts = path.Split('.', StringSplitOptions.RemoveEmptyEntries);
 18181            return root.GetDeepPropertyValue(parts);
 182        }
 183
 184        /// <summary>
 185        /// Gets the value of a nested property specified by a list of property names, using reflection to traverse the 
 186        /// </summary>
 187        /// <param name="path">The list of property names representing the path of the nested property.</param>
 188        /// <returns>The value of the nested property, or null if any part of the path is invalid or if the final value 
 189        public object? GetDeepPropertyValue(IReadOnlyList<string> path)
 190        {
 18191            var current = root;
 192
 78193            for (var i = 0; i < path.Count && current is not null; i++)
 194            {
 24195                var type = current.GetType();
 24196                var map = GetCachedPropertyMap(type);
 197
 24198                if (!map.TryGetValue(path[i], out var property))
 3199                    return null;
 200
 21201                current = property.GetValue(current);
 202            }
 203
 15204            return current;
 205        }
 206
 207        /// <summary>
 208        /// Gets the value of a nested property specified by a dot-separated path, using reflection to traverse the obje
 209        /// </summary>
 210        /// <param name="path">The dot-separated path of the nested property.</param>
 211        /// <typeparam name="T">The type to cast the final value to.</typeparam>
 212        /// <returns>The value of the nested property cast to the specified type, or null if any part of the path is inv
 3213        public T? GetDeepPropertyValue<T>(string path) => (T?)root.GetDeepPropertyValue(path);
 214    }
 215
 216    extension(IEnumerable<PropertyInfo> properties)
 217    {
 218        /// <summary>
 219        /// Gets the values of the properties of the specified type from the given instance, using reflection to check e
 220        /// </summary>
 221        /// <param name="instance">The instance from which to retrieve the property values.</param>
 222        /// <typeparam name="T">The type of the property values to retrieve.</typeparam>
 223        /// <returns>An enumerable of the property values that match the specified type, or an empty enumerable if no pr
 224        public IEnumerable<T?> GetValuesOfType<T>(object? instance)
 225        {
 6226            ArgumentNullException.ThrowIfNull(properties);
 227
 6228            return getValuesOfType();
 229
 230            IEnumerable<T?> getValuesOfType()
 231            {
 232                if (instance is null)
 233                    yield break;
 234
 235                foreach (var property in properties)
 236                {
 237                    if (typeof(T).IsAssignableFrom(property.PropertyType))
 238                    {
 239                        var value = property.GetValue(instance);
 240                        if (value is T typed)
 241                            yield return typed;
 242                    }
 243                }
 244            }
 245        }
 246    }
 247
 248    #region Property caches
 249
 9250    private static readonly ConcurrentDictionary<Type, PropertyInfo[]> PublicPropertiesCache = new();
 9251    private static readonly ConcurrentDictionary<(Type Type, Type Attribute), PropertyInfo[]> AttributeCache = new();
 9252    private static readonly ConcurrentDictionary<Type, Dictionary<string, PropertyInfo>> PropertyMapCache = new();
 253
 254    private static PropertyInfo[] GetCachedPublicProperties(Type type)
 585255        => PublicPropertiesCache.GetOrAdd(type, static t =>
 585256            t.GetProperties(BindingFlags.Instance | BindingFlags.Public));
 257
 258    private static PropertyInfo[] GetCachedPublicPropertiesWithAttribute(Type type, Type attributeType)
 259    {
 6260        var key = (type, attributeType);
 261
 6262        return AttributeCache.GetOrAdd(key, static k =>
 6263        {
 6264            var (t, attrType) = k;
 6265
 6266            var properties = GetCachedPublicProperties(t);
 6267
 6268            return [.. properties.Where(p => p.IsDefined(attrType, inherit: true))];
 6269        });
 270    }
 271
 272    private static Dictionary<string, PropertyInfo> GetCachedPropertyMap(Type type)
 30273        => PropertyMapCache.GetOrAdd(type, static t =>
 30274        {
 30275            var dict = new Dictionary<string, PropertyInfo>(StringComparer.Ordinal);
 30276
 30277            foreach (var prop in t.GetProperties(BindingFlags.Instance | BindingFlags.Public))
 30278            {
 30279                dict[prop.Name] = prop;
 30280            }
 30281
 30282            return dict;
 30283        });
 284
 285    #endregion
 286}
 287