// Copyright 2009-2022 Josh Close // This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0. // See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0. // https://github.com/JoshClose/CsvHelper using System; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; using System.Reflection; using System.Runtime.CompilerServices; using CsvHelper.Configuration; using CsvHelper.Configuration.Attributes; namespace CsvHelper { /// /// Common reflection tasks. /// internal static class ReflectionHelper { /// /// Gets the from the type where the property was declared. /// /// The type the property belongs to. /// The property to search. /// Flags for how the property is retrieved. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static PropertyInfo GetDeclaringProperty(Type type, PropertyInfo property, BindingFlags flags) { if (property.DeclaringType != type) { var declaringProperty = property.DeclaringType.GetProperty(property.Name, flags); return GetDeclaringProperty(property.DeclaringType, declaringProperty, flags); } return property; } /// /// Gets the from the type where the field was declared. /// /// The type the field belongs to. /// The field to search. /// Flags for how the field is retrieved. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static FieldInfo GetDeclaringField(Type type, FieldInfo field, BindingFlags flags) { if (field.DeclaringType != type) { var declaringField = field.DeclaringType.GetField(field.Name, flags); return GetDeclaringField(field.DeclaringType, declaringField, flags); } return field; } /// /// Walk up the inheritance tree collecting properties. This will get a unique set of properties in the /// case where parents have the same property names as children. /// /// The to get properties for. /// The flags for getting the properties. /// If true, parent class properties that are hidden by `new` child properties will be overwritten. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static List GetUniqueProperties(Type type, BindingFlags flags, bool overwrite = false) { var ignoreBase = type.GetCustomAttribute(typeof(IgnoreBaseAttribute)) != null; var properties = new Dictionary(); flags |= BindingFlags.DeclaredOnly; var currentType = type; while (currentType != null) { var currentProperties = currentType.GetProperties(flags); foreach (var property in currentProperties) { if (!properties.ContainsKey(property.Name) || overwrite) { properties[property.Name] = property; } } if (ignoreBase) { break; } currentType = currentType.BaseType; } return properties.Values.ToList(); } /// /// Walk up the inheritance tree collecting fields. This will get a unique set of fields in the /// case where parents have the same field names as children. /// /// The to get fields for. /// The flags for getting the fields. /// If true, parent class fields that are hidden by `new` child fields will be overwritten. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static List GetUniqueFields(Type type, BindingFlags flags, bool overwrite = false) { var ignoreBase = type.GetCustomAttribute(typeof(IgnoreBaseAttribute)) != null; var fields = new Dictionary(); flags |= BindingFlags.DeclaredOnly; var currentType = type; while (currentType != null) { var currentFields = currentType.GetFields(flags); foreach (var field in currentFields) { if (!fields.ContainsKey(field.Name) || overwrite) { fields[field.Name] = field; } } if (ignoreBase) { break; } currentType = currentType.BaseType; } return fields.Values.ToList(); } /// /// Gets the property from the expression. /// /// The type of the model. /// The type of the property. /// The expression. /// The for the expression. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static MemberInfo GetMember(Expression> expression) { var member = GetMemberExpression(expression.Body).Member; var property = member as PropertyInfo; if (property != null) { return property; } var field = member as FieldInfo; if (field != null) { return field; } throw new ConfigurationException($"'{member.Name}' is not a member."); } /// /// Gets the member inheritance chain as a stack. /// /// The type of the model. /// The type of the property. /// The member expression. /// The inheritance chain for the given member expression as a stack. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Stack GetMembers(Expression> expression) { var stack = new Stack(); var currentExpression = expression.Body; while (true) { var memberExpression = GetMemberExpression(currentExpression); if (memberExpression == null) { break; } stack.Push(memberExpression.Member); currentExpression = memberExpression.Expression; } return stack; } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static MemberExpression? GetMemberExpression(Expression expression) { MemberExpression? memberExpression = null; if (expression.NodeType == ExpressionType.Convert) { var body = (UnaryExpression)expression; memberExpression = body.Operand as MemberExpression; } else if (expression.NodeType == ExpressionType.MemberAccess) { memberExpression = expression as MemberExpression; } return memberExpression; } } }