我正在按照這個SO答案將lambda表達式轉換為部分SQL語法。
但是,我在解析Contains
的表達式時遇到問題。我添加了一個方法:
private bool ParseContainsExpression(MethodCallExpression expression)
{
MemberExpression member = (MemberExpression)expression.Arguments[0];
var methodInfo = typeof(List<int>).GetMethod("Contains", new Type[] { typeof(int) });
//TODO check if list contains value
return false;
}
由於我對錶達式完全陌生,我不知道從哪裡獲取屬性名稱和值,以及包含我想要檢查的值的列表。這些屬性和值存儲在表達式中的哪個位置?
基本上,您需要處理Method , Object ( 表示實例方法調用的實例的表達式或靜態方法調用的null )和MethodCallExpression類的Arguments ( 表示被調用方法的參數的表達式集合 )屬性。
特別是對於Contains
,你需要避免(或者在需要時以不同的方式處理) string.Contains
方法,並且還處理像Enumerable.Contains
這樣的static
方法以及ICollection<T>.Contains
, List<T>.Contains
等實例方法。為了獲得列表值(如果可能的話),你必須找到某種常量表達式。
這是一個示例:
private bool ParseContainsExpression(MethodCallExpression expression)
{
// The method must be called Contains and must return bool
if (expression.Method.Name != "Contains" || expression.Method.ReturnType != typeof(bool)) return false;
var list = expression.Object;
Expression operand;
if (list == null)
{
// Static method
// Must be Enumerable.Contains(source, item)
if (expression.Method.DeclaringType != typeof(Enumerable) || expression.Arguments.Count != 2) return false;
list = expression.Arguments[0];
operand = expression.Arguments[1];
}
else
{
// Instance method
// Exclude string.Contains
if (list.Type == typeof(string)) return false;
// Must have a single argument
if (expression.Arguments.Count != 1) return false;
operand = expression.Arguments[0];
// The list must be IEnumerable<operand.Type>
if (!typeof(IEnumerable<>).MakeGenericType(operand.Type).IsAssignableFrom(list.Type)) return false;
}
// Try getting the list items
object listValue;
if (list.NodeType == ExpressionType.Constant)
// from constant value
listValue = ((ConstantExpression)list).Value;
else
{
// from constant value property/field
var listMember = list as MemberExpression;
if (listMember == null) return false;
var listOwner = listMember.Expression as ConstantExpression;
if (listOwner == null) return false;
var listProperty = listMember.Member as PropertyInfo;
listValue = listProperty != null ? listProperty.GetValue(listOwner.Value) : ((FieldInfo)listMember.Member).GetValue(listOwner.Value);
}
var listItems = listValue as System.Collections.IEnumerable;
if (listItems == null) return false;
// Do whatever you like with listItems
return true;
}
您的實現與示例答案完全不同。您確實需要從ExpressionVisitor
繼承,以便您可以正確解析樹。
我們以此表達式為例:
var myList = new List<string> { "A" };
Expression<Func<string, bool>> a = (s) => myList.Contains(s);
ParseContainsExpression(a.Body as MethodCallExpression);
private bool ParseContainsExpression(MethodCallExpression expression)
{
expression.Object; //myList
expression.Arguments[0]; //s
return false;
}
但請注意,這些仍然是表達式 ,它們還不是實際值。您需要調用表達式來獲取值。
但是,在我們的例子中 - myList
實際上是一個ConstantExpression
。所以我們可以這樣做:
((expression.Object as MemberExpression).Expression as ConstantExpression).Value; //myList
這將返回原始列表。請注意,它是一個字段訪問,因為表達式被編譯成一個閉包,它將myList
作為一個字段放在閉包類中。正如您所看到的,我們必須對我們正在處理的Expression類型做出很多假設(它是一個字段訪問,然後是一個常量表達式)。你真的需要一個完全成熟的訪問者實現來正確地做到這一點(鏈接的答案描述)