识别 LINQ 表达式中的索引器

本文关键字:索引 LINQ 表达式 识别 | 更新日期: 2023-09-27 18:02:35

我需要以编程方式识别表达式中何时出现索引器,但生成的表达式树不是我预期的。

class IndexedPropertiesTest
{
    static void Main( string[] args ) { new IndexedPropertiesTest(); }
    public string this[int index]
    {
        get { return list[index]; }
        set { list[index] = value; }
    }
    List<string> list = new List<string>();
    public IndexedPropertiesTest()
    {
        Test( () => this[0] );
    }
    void Test( Expression<Func<string>> expression )
    {
        var nodeType = expression.Body.NodeType;
        var methodName = ((MethodCallExpression)expression.Body).Method.Name;
    }
}

在上面的代码中,nodeType是"Call",methodName是"get_Item"。 为什么? expression.Body不应该等同于Expression.Property( Expression.Constant( this ), "Item", Expression.Constant( 0 ) )吗? 这就是我所期望的。

我需要能够以非常通用的方式检测索引器 - 几乎给定任何表达式。 这种对预期表达式树的篡改损害了我这样做的能力。 依赖方法名称为"get_Item"太脆弱了。 另外,无论如何,IndexerNameAttribute可能已用于重命名索引器属性。

那么有没有办法让编译器生成预期的表达式树呢? 请不要建议手动构建表达式,因为这不是一个选项。 或者有没有办法以编程方式确定我拥有的是索引器?

识别 LINQ 表达式中的索引器

我想你有你的答案:这就是C#编译器的工作方式。

我把你的代码翻译成 VB.NET。无济于事的是,VB.NET 不会将该方法称为 get_Item ,而是通过您给它命名来调用它。在下面的示例中,最终以 get_MyDefaultProperty .

Sub Main
    Dim x as IndexedPropertiesTest = New IndexedPropertiesTest()
End Sub
' Define other methods and classes here
Class IndexedPropertiesTest
    Private list as New List(Of String) From { "a" }
    Default Property MyDefaultProperty(index as Integer) as String
        Get
            Return list(index)
        End Get
        Set(value as String)
            list(index) = value
        End Set
    End Property
    Public Sub New
        Test( Function() Me(0))
    End Sub
    Public Sub Test(expression as Expression(Of Func(Of String)))
        Dim nodeType as ExpressionType = expression.Body.NodeType
        Dim methodName as String = CType(expression.Body, MethodCallExpression).Method.Name
        'expression.Dump() 'Using LINQPad
    End Sub
End Class

但是,一切都不会丢失:您可以编写一个访问者来尝试将get_Item调用塞回IndexExpression。我从这里开始:

public class PropertyFixerVisitor : ExpressionVisitor
{
    protected override Expression VisitMethodCall(MethodCallExpression node)
    {
        if (node.Method.Name.StartsWith("get_"))
        {
            var possibleProperty = node.Method.Name.Substring(4);
            var properties = node.Method.DeclaringType.GetProperties()
                .Where(p => p.Name == possibleProperty);
            //HACK: need to filter out for overriden properties, multiple parameter choices, etc.
            var property = properties.FirstOrDefault();
            if (property != null)
                return Expression.Property(node.Object, possibleProperty, node.Arguments.ToArray());
            return base.VisitMethodCall(node);
        }
        else
            return base.VisitMethodCall(node);
    }
}

然后,您可以安全地修改测试方法,如下所示:

void Test(Expression<Func<string>> expression)
{
    var visitor = new PropertyFixerVisitor();
    var modExpr = (Expression<Func<string>>)visitor.Visit(expression);
    var indexExpression = (modExpr.Body as IndexExpression); //Not Null
}

我最近遇到了同样的问题,最终得到了以下解决方案(给定您的示例(:

void Test(Expression<Func<string>> expression)
{
    if (expression.Body.NodeType == ExpressionType.Call)
    {
        var callExpression = (MethodCallExpression)expression.Body;
        var getMethod = callExpression.Method;
        var indexer = getMethod.DeclaringType.GetProperties()
            .FirstOrDefault(p => p.GetGetMethod() == getMethod);
        if (indexer == null)
        {
            // Not indexer access
        }
        else
        {
            // indexer is a PropertyInfo accessed by expression
        }
    }
}

因此,基本上,我不依赖于索引器以特定方式命名,而是依赖于以下内容:

  1. 两个MethodInfo对象可以与相等(或不平等(进行比较(operator ==operator !=都是为MethodInfo实现的(。
  2. 索引器有一个 get 方法。在一般情况下,属性可能没有 get 方法,但此类属性首先不能用于构造具有 lambda 语法的表达式。
  3. 对非索引器属性的访问会产生MemberExpression而不是MethodCallExpression(即使不会,表示简单属性PropertyInfo始终可以与使用 GetIndexParameters 方法表示索引器的区分开来,因为所有索引器都至少有一个参数(。

当一个类中索引器 exst 的多个重载时,此方法也有效,因为它们中的每一个都有自己的MethodInfo并且只有一个重载等于表达式中使用的重载。

注意:上述方法既不适用于私有索引器,也不适用于具有私有 get 方法的索引器。为了概括该方法,应该使用正确的GetPropertiesGetGetMethod重载:

// ...
var indexer = getMethod.DeclaringType.GetProperties(
        BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
    .FirstOrDefault(p => p.GetGetMethod(nonPublic: true) == getMethod);
// ...

希望这会帮助某人。