'n Abend,
ich bin seit einer Weile ziemlich oft in der Situation, dass ich die MemberInfo von einem bestimmten Member brauche.
Klar, kann ich hin gehen und per Reflection und mit dem Namen vom Typ den Member abrufen, aber das ist mMn. ziemlich unleserlich.
Viel schöner finde ich dagegen, wenn ich das ganze als LambdaExpression lösen kann, z.B. so:
C#-Code: |
var myPropertyInfo = Reflect.Property(() => this.MyProperty);
var propertyInfoList = Reflect.PropertyChain(() => this.P1.P2.P3);
object instance;
var myPropertyInfo = Reflect.Property(() => this.MyProperty, out instance);
object instance;
var propertyInfoList = Reflect.PropertyChain(() => this.P1.P2.P3, out instance);
|
Ich hab gerade eben den letzten Test für diese Reflect-Klasse ans Laufen gebracht und dachte mir, ich lass die Allgemeinheit dran teil haben - vielleicht hilft's ja jemandem.
Ich gebe zu, den Namen der Klasse habe ich mir bei CSLA abgeschaut, der Inhalt stammt aber ausschließlich von mir.
Die eigentliche Magie befindet sich in der Klasse ExpressionReflector.
"Einstieg" sind dabei die Methoden "Member" und "MemberChain", die werden den zig Überladungen in der Reflect-Klasse aufgerufen.
Der komplette Code befindet sich im Anhang, dazu aber unten mehr Infos.
C#-Code: |
public static class ExpressionReflector
{
public static Type[] SupportedMemberTypes { get; } = new[]
{
typeof(FieldInfo),
typeof(PropertyInfo),
typeof(MethodInfo),
};
public static TMemberInfo Member<TMemberInfo>(LambdaExpression memberLambdaExpression)
where TMemberInfo : MemberInfo
{
object _;
return Member<TMemberInfo>(memberLambdaExpression, false, out _);
}
public static TMemberInfo Member<TMemberInfo>(LambdaExpression memberLambdaExpression, out object instance)
where TMemberInfo : MemberInfo
{
return Member<TMemberInfo>(memberLambdaExpression, true, out instance);
}
private static TMemberInfo Member<TMemberInfo>(LambdaExpression memberLambdaExpression, bool getInstance, out object instance)
where TMemberInfo : MemberInfo
{
if (SupportedMemberTypes.Contains(typeof(TMemberInfo)))
return MemberChain<TMemberInfo>(memberLambdaExpression, getInstance, out instance).Last();
else
throw CreateNotSupportedMemberInfoType<TMemberInfo>();
}
public static IEnumerable<TMemberInfo> MemberChain<TMemberInfo>(LambdaExpression memberLambdaExpression)
where TMemberInfo : MemberInfo
{
object _;
return MemberChain<TMemberInfo>(memberLambdaExpression, false, out _);
}
public static IEnumerable<TMemberInfo> MemberChain<TMemberInfo>(LambdaExpression memberLambdaExpression, out object instance)
where TMemberInfo : MemberInfo
{
return MemberChain<TMemberInfo>(memberLambdaExpression, true, out instance);
}
private static IEnumerable<TMemberInfo> MemberChain<TMemberInfo>(LambdaExpression memberLambdaExpression, bool getInstance, out object instance)
where TMemberInfo : MemberInfo
{
if (SupportedMemberTypes.Contains(typeof(TMemberInfo)))
{
var bodyExpression = GetPureExpressionBody(memberLambdaExpression);
var isIndexerChain = typeof(TMemberInfo) == typeof(PropertyInfo)
&& bodyExpression is MethodCallExpression
&& IsIndexerGetter((bodyExpression as MethodCallExpression).Method);
if (typeof(TMemberInfo) == typeof(MethodInfo) || isIndexerChain)
{
var methodChain = GetMethodChain(memberLambdaExpression, getInstance, out instance);
return isIndexerChain
? methodChain.Select(GetIndexer) as IEnumerable<TMemberInfo>
: methodChain as IEnumerable<TMemberInfo>;
}
else if (bodyExpression is MemberExpression)
{
return GetMemberChain<TMemberInfo>(bodyExpression, getInstance, out instance);
}
else
throw CreateNotSupportedMemberChainExpression<TMemberInfo>(getInstance);
}
else
throw CreateNotSupportedMemberInfoType<TMemberInfo>();
}
private static IEnumerable<TMemberInfo> GetMemberChain<TMemberInfo>(Expression bodyExpression, bool getInstance, out object instance)
where TMemberInfo : MemberInfo
{
var expressionChain = BuildExpressionChain(bodyExpression as MemberExpression, exp => exp.Expression).Reverse();
instance = getInstance
? ExtractMemberInstance(ref expressionChain)
: null;
var memberChain = expressionChain
.SkipWhile(exp => !(exp is MemberExpression) || !((exp as MemberExpression).Member is TMemberInfo))
.Cast<MemberExpression>()
.Select(m => (TMemberInfo)m.Member);
var isFirstMemberLocalInstanceField = memberChain
.First()
.DeclaringType.GetTypeInfo()
.GetCustomAttribute<CompilerGeneratedAttribute>() != null;
if (isFirstMemberLocalInstanceField)
memberChain = memberChain.Skip(1);
if (!memberChain.Any())
CreateNotSupportedMemberExpression<TMemberInfo>(getInstance);
return memberChain;
}
private static IEnumerable<MethodInfo> GetMethodChain(LambdaExpression memberLambdaExpression, bool getInstance, out object instance)
{
var bodyExpression = GetPureExpressionBody(memberLambdaExpression);
var callExpression = bodyExpression as MethodCallExpression;
if (callExpression != null)
{
var callChain = BuildExpressionChain(callExpression, exp => exp.Object).Reverse();
instance = getInstance
? ExtractMethodInstance(ref callChain)
: null;
var methods = callChain
.SkipWhile(exp => !(exp is MethodCallExpression))
.Cast<MethodCallExpression>()
.Select(exp => exp.Method);
if (!methods.Any())
CreateNotSupportedMemberExpression<MethodInfo>(getInstance);
return methods;
}
else
throw CreateNotSupportedMemberChainExpression<MethodInfo>(getInstance);
}
private static object ExtractMemberInstance(ref IEnumerable<Expression> expressionChain)
{
object instance;
var instanceMemberExpression = expressionChain.First();
if (instanceMemberExpression is MemberExpression)
{
instance = null;
}
else if (instanceMemberExpression is ConstantExpression)
{
expressionChain = expressionChain.Skip(1);
var sourceExpression = instanceMemberExpression as ConstantExpression;
if (sourceExpression.Type.GetTypeInfo().GetCustomAttribute<CompilerGeneratedAttribute>() != null)
{
instanceMemberExpression = expressionChain.FirstOrDefault() as MemberExpression;
expressionChain = expressionChain.Skip(1);
if (instanceMemberExpression is MemberExpression)
{
var staticInstanceMember = (instanceMemberExpression as MemberExpression).Member;
if (staticInstanceMember is FieldInfo)
instance = (staticInstanceMember as FieldInfo).GetValue(sourceExpression.Value);
else
throw CreateNotSupportedInstanceMemberExpression();
}
else
throw CreateNotSupportedInstanceMemberExpression();
}
else
instance = sourceExpression.Value;
}
else
throw CreateNotSupportedInstanceMemberExpression();
return instance;
}
private static object ExtractMethodInstance(ref IEnumerable<Expression> callChain)
{
object instance;
var first = callChain.First();
if (first is MethodCallExpression)
{
instance = null;
}
else
{
if (first is ConstantExpression)
{
instance = (first as ConstantExpression).Value;
}
else if (first is MemberExpression)
{
var instanceMemberExpression = first as MemberExpression;
if (instanceMemberExpression.Member is FieldInfo)
{
var instanceMemberInstance = (instanceMemberExpression.Expression as ConstantExpression).Value;
instance = (instanceMemberExpression.Member as FieldInfo).GetValue(instanceMemberInstance);
}
else
throw CreateNotSupportedInstanceMemberExpression();
}
else
throw CreateNotSupportedInstanceMemberExpression();
callChain = callChain.Skip(1);
}
return instance;
}
private static Expression GetPureExpressionBody(LambdaExpression memberLambdaExpression)
{
var bodyExpression = null as Expression;
if (memberLambdaExpression.Body is MemberExpression)
bodyExpression = memberLambdaExpression.Body;
else if (memberLambdaExpression.Body is MethodCallExpression)
bodyExpression = memberLambdaExpression.Body;
return bodyExpression;
}
private static NotSupportedException CreateNotSupportedInstanceMemberExpression()
{
return new NotSupportedException($"Not supported expression. The instance source must be a field, a property or a local variable");
}
private static NotSupportedException CreateNotSupportedMemberInfoType<TMemberInfo>()
where TMemberInfo : MemberInfo
{
return new NotSupportedException($"Not supported MemberInfo-Type: {typeof(TMemberInfo)}");
}
private static NotSupportedException CreateNotSupportedMemberExpression<TMemberInfo>(bool withInstance)
{
var instanceMember = withInstance ? "obj." : "";
var memberName = typeof(TMemberInfo).Name.Replace("Info", "");
if (typeof(TMemberInfo) == typeof(MemberInfo))
memberName += "()";
return new NotSupportedException($"Not supported expression. Expression musst be like: () => {instanceMember}{memberName}");
}
private static NotSupportedException CreateNotSupportedMemberChainExpression<TMemberInfo>(bool withInstance)
{
var instanceMember = withInstance ? "obj." : "";
var memberName = typeof(TMemberInfo).Name.Replace("Info", "");
if (typeof(TMemberInfo) == typeof(MemberInfo))
memberName += "()";
return new NotSupportedException($"Not supported expression. Expression musst be like: () => {instanceMember}{memberName}1.{memberName}2.{memberName}3");
}
private static IEnumerable<Expression> BuildExpressionChain<TExpression>(TExpression expression, Func<TExpression, Expression> getPreviousExpression)
where TExpression : Expression
{
yield return expression;
var previousExpression = getPreviousExpression(expression);
if (previousExpression is UnaryExpression)
{
var unaryExpression = previousExpression as UnaryExpression;
if (unaryExpression.NodeType == ExpressionType.Convert)
previousExpression = unaryExpression.Operand;
else
throw new Exception();
}
if (previousExpression is TExpression)
{
foreach (var previous in BuildExpressionChain((TExpression)previousExpression, getPreviousExpression))
yield return previous;
}
else if (previousExpression != null)
yield return previousExpression;
}
private static bool IsIndexerGetter(MethodInfo getItemMethod)
{
return getItemMethod.DeclaringType.GetRuntimeProperties().Any(p => p.GetMethod == getItemMethod);
}
private static PropertyInfo GetIndexer(MethodInfo getItemMethod)
{
return getItemMethod.DeclaringType.GetRuntimeProperties().Single(p => p.GetMethod == getItemMethod);
}
}
|
Im Anhang gibts ein mit Visual Studio 2017 gebautes Projekt mit dieser Klasse, der tatsächlichen Reflect-Klasse und allen Tests.
Das Zielframework ist .NET Standard 1.2, in der Hoffnung, dass ich damit alle aktuellen Frameworks bestmöglich abdecken kann.
Wer noch Wünsche an Funktionen hat, mich auf fehlende Test Cases hinweisen will oder allgemein Verbesserungsvorschläge hat, darf sich gerne auch melden.
Schlagwörter: .NET Standard, LambdaExpression, Lambda, Expression, Reflection, MemberInfo, MethodInfo, PropertyInfo, FieldInfo