// 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;
}
}
}