识别 LINQ 表达式中的索引器
Recognize indexer in LINQ expression
我需要以编程方式识别索引器何时出现在表达式中,但生成的表达式树不是我所期望的。
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
可能已被用来重命名索引器 属性。
那么有没有办法让编译器生成预期的表达式树呢?请不要建议手动构建表达式,因为那不是一个选项。或者有什么方法可以以编程方式确保我拥有的是索引器?
我想您已经有了答案:这就是 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
}
}
}
所以,基本上,我不依赖以特定方式命名的索引器,而是依赖以下内容:
MethodInfo
的两个对象可以比较为相等(或不等)(operator ==
and operator !=
都为MethodInfo
实现)。
- 索引器有一个get方法。在一般情况下,属性 可能没有 get 方法,但这样的 属性 不能首先用于构造具有 lambda 语法的表达式。
- 访问非索引器-属性 产生
MemberExpression
而不是 MethodCallExpression
(即使不会,PropertyInfo
代表简单的 属性始终可以通过 GetIndexParameters 方法与代表索引器区分开来,因为所有索引器都至少有一个参数)。
如果 class 中存在多个索引器重载,此方法也适用,因为它们中的每一个都有自己的 MethodInfo
,并且只有一个等于表达式中使用的那个。
注意:上述方法既不适用于私有索引器,也不适用于具有私有 get 方法的索引器。要推广该方法,应该使用 GetProperties
和 GetGetMethod
:
的正确重载
// ...
var indexer = getMethod.DeclaringType.GetProperties(
BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
.FirstOrDefault(p => p.GetGetMethod(nonPublic: true) == getMethod);
// ...
希望对某人有所帮助。
我需要以编程方式识别索引器何时出现在表达式中,但生成的表达式树不是我所期望的。
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
可能已被用来重命名索引器 属性。
那么有没有办法让编译器生成预期的表达式树呢?请不要建议手动构建表达式,因为那不是一个选项。或者有什么方法可以以编程方式确保我拥有的是索引器?
我想您已经有了答案:这就是 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
}
}
}
所以,基本上,我不依赖以特定方式命名的索引器,而是依赖以下内容:
MethodInfo
的两个对象可以比较为相等(或不等)(operator ==
andoperator !=
都为MethodInfo
实现)。- 索引器有一个get方法。在一般情况下,属性 可能没有 get 方法,但这样的 属性 不能首先用于构造具有 lambda 语法的表达式。
- 访问非索引器-属性 产生
MemberExpression
而不是MethodCallExpression
(即使不会,PropertyInfo
代表简单的 属性始终可以通过 GetIndexParameters 方法与代表索引器区分开来,因为所有索引器都至少有一个参数)。
如果 class 中存在多个索引器重载,此方法也适用,因为它们中的每一个都有自己的 MethodInfo
,并且只有一个等于表达式中使用的那个。
注意:上述方法既不适用于私有索引器,也不适用于具有私有 get 方法的索引器。要推广该方法,应该使用 GetProperties
和 GetGetMethod
:
// ...
var indexer = getMethod.DeclaringType.GetProperties(
BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
.FirstOrDefault(p => p.GetGetMethod(nonPublic: true) == getMethod);
// ...
希望对某人有所帮助。