假设我有以下扩展方法和支持类,并用类似的方式调用它:
AppLogs = await Context.PageAsync<AppLog>(log => log.IsActive == true && log.Text == "foo");
PageAync 方法不相关,但它最终会调用 GetMenebers:
public static IReadOnlyList<KeyValuePair<T, object?>> GetMembers<T>(this Expression<Func<T, object?>> expression) where T : class, new()
{
var visitor = new PropertyVisitor<T>();
visitor.Visit(expression.Body);
visitor.Path.Reverse();
return visitor.Path;
}
internal class PropertyVisitor<T> : ExpressionVisitor
{
internal readonly List<KeyValuePair<T, object?>> Path = new();
protected override Expression VisitMember(MemberExpression node)
{
if (!(node.Member is PropertyInfo info))
throw new ArgumentException("The path can only contain properties", nameof(node));
Path.Add(new KeyValuePair<T, object>(((PropertyInfo)member).Name, ((PropertyInfo)member).GetValue(***NEED OBJ***))));
return base.VisitMember(node);
}
}
public class AppLog()
{
public string Text { get; set; }
public bool IsActive { get; set; }
}
访问者所需的结果。路径将是两个键值对:
Key: "IsActive", Value: true
Key: "Text", Value: "Foo"
由于调用本身没有实例对象,通过类型推断参数,有没有办法获取这两个属性值?
我假设如果有一个方法,它不会是通过使用PropertyInfo。但我不知道另一种反思方式。
本质上,我需要的几乎正是此处所看到的内容,但需要的是值,而不是名称。
更新
我认为修改我的访问者应该可行。很快就会更新。
internal class PropertyVisitor<T> : ExpressionVisitor
{
internal readonly List<KeyValuePair<T, object?>> Path = new();
protected override Expression VisitMember(MemberExpression node)
{
// if (!(node.Member is PropertyInfo info))
// throw new ArgumentException("The path can only contain properties", nameof(node));
Path.AddRange(ExtractConstants(node));
return base.VisitMember(node);
}
private static IEnumerable<KeyValuePair<string, object?>> ExtractConstants(MemberExpression memberExpression)
{
var constants = new List<KeyValuePair<string, object?>>();
var constExpression = (ConstantExpression)memberExpression.Expression;
var valIsConstant = constExpression != null;
Type declaringType = memberExpression.Member.DeclaringType;
object declaringObject = memberExpression.Member.DeclaringType;
if (valIsConstant)
{
declaringType = constExpression.Type;
declaringObject = constExpression.Value;
}
var member = declaringType.GetMember(memberExpression.Member.Name, MemberTypes.Field | MemberTypes.Property, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static).Single();
if (member.MemberType == MemberTypes.Field)
constants.Add(new KeyValuePair<string, object>(((FieldInfo)member).Name, ((FieldInfo)member).GetValue(declaringObject)));
else
constants.Add(new KeyValuePair<string, object>(((FieldInfo)member).Name, ((PropertyInfo)member).GetGetMethod(true).Invoke(declaringObject, null)));
return constants;
}
}
在表达式
log => log.IsActive == true && log.Text == "foo"
等式 (==
) 的示例中,是一个 BinaryExpression,所以让我们在访问者中捕获二进制表达式:
internal class PropertyVisitor<T> : ExpressionVisitor
{
internal readonly List<KeyValuePair<string, object>> Path = new();
protected override Expression VisitBinary(BinaryExpression node)
{
if(node.NodeType == ExpressionType.Equal) {
ConstantExpression constantExpression = null;
MemberExpression memberExpression = null;
if(node.Right is ConstantExpression right) {
constantExpression = right;
memberExpression = (MemberExpression)node.Left;
} else if(node.Left is ConstantExpression left) {
constantExpression = left;
memberExpression = (MemberExpression)node.Right;
}
if(constantExpression == null)
throw new ArgumentException("Only equality to constants supported", nameof(node));
if(memberExpression == null)
throw new ArgumentException("The path can only contain properties", nameof(node));
string name = ((MemberInfo)memberExpression.Member).Name;
Object @value = constantExpression.Value;
Path.Add(new KeyValuePair<string, object>(name, @value));
}
return base.VisitBinary(node);
}
}
然后称为
var result = GetMembers<AppLog>(log => log.IsActive == true && log.Text == "foo");
Console.WriteLine(String.Join(Environment.NewLine, result.Select(kv => $"{kv.Key} = {kv.Value}")));
它产生输出:
Text = foo
IsActive = True