(转)lambda表达式的解析(五) Lambda表达式与闭包类型
2011-10-13 22:47
429 查看
在讲述如何解析转换成员访问表达式之前,先来讲一些预备知识。
一个标准的lambda表达式应该是
( 参数列表 ) => 表达式或表达式块
其中参数列表和方法的参数列表类似,不过lambda表达式更灵活,允许用的时候不用显示声明参数的类型甚至在一个参数的时候括号也可以不用加,也就是(int a) => ... 和 (a)=> ... 以及 a=> ... 都是允许的。
我们之前说了不少表达式的解析,都是针对lambda表达式的右半边进行的。对于左半边的参数列表的解析是非常简单的,不过最大的问题在于解析之后如何把参数应用到表达式中。从语言特性上来说,C#里所有变量是不会影响在它范围之外的区域的,它只对在它范围包括所有子语句体的范围产生影响,所以我们如果在解析的时候都能得到当前及父语句块内的参数就可以了,不需要考虑子块产生新变量对父块的影响:
view plaincopy to clipboardprint?
int a = 2;
Func<int, Func<int>> f1 = (b) => () => b + a;
Func<Func<int, int>> f2 = () => b => b + a;
view plaincopy to clipboardprint?
private List<Type> _paramTypes = new List<Type>();
public Expression<TDelegate> Parse<TDelegate>(string expression)
{
_paramTypes.Clear();
var genericParameters = typeof(TDelegate).GetGenericTypeDefinition().GetGenericArguments();
var realArguments = typeof(TDelegate).GetGenericArguments();
_paramTypes.AddRange(
realArguments.Where((a, i) =>
genericParameters.First(p => p.GenericParameterPosition == i).
GenericParameterAttributes.HasFlag(GenericParameterAttributes.Contravariant)));
var exp = Parse(expression);
var genericlambda = exp as Expression<TDelegate>;
if (genericlambda != null)
return genericlambda;
var lambda = exp as LambdaExpression;
if (lambda != null)
{
return Expression.Lambda<TDelegate>(lambda.Body, lambda.Parameters);
}
return Expression.Lambda<TDelegate>(exp);
}
view plaincopy to clipboardprint?
private Dictionary<string, object> _knownVariables = new Dictionary<string, object>();
public ExpressionParser With(Expression<Func<object>> variable)
{
var name = variable.Body.ToReadableString();
var @var = variable.Compile()();
return With(name, @var);
}
public ExpressionParser With(string name, object variable)
{
var parser = new ExpressionParser();
parser._knownVariables.Add(name, variable);
knownTypes.TryAdd(variable.GetType().Name, variable.GetType());
return parser;
}
一个标准的lambda表达式应该是
( 参数列表 ) => 表达式或表达式块
其中参数列表和方法的参数列表类似,不过lambda表达式更灵活,允许用的时候不用显示声明参数的类型甚至在一个参数的时候括号也可以不用加,也就是(int a) => ... 和 (a)=> ... 以及 a=> ... 都是允许的。
我们之前说了不少表达式的解析,都是针对lambda表达式的右半边进行的。对于左半边的参数列表的解析是非常简单的,不过最大的问题在于解析之后如何把参数应用到表达式中。从语言特性上来说,C#里所有变量是不会影响在它范围之外的区域的,它只对在它范围包括所有子语句体的范围产生影响,所以我们如果在解析的时候都能得到当前及父语句块内的参数就可以了,不需要考虑子块产生新变量对父块的影响:
view plaincopy to clipboardprint?
int a = 2;
Func<int, Func<int>> f1 = (b) => () => b + a;
Func<Func<int, int>> f2 = () => b => b + a;
view plaincopy to clipboardprint? private Expression ProcessLambdaExpression(ParseTreeNode expNode) { List<ParameterExpression> arglist = new List<ParameterExpression>(); var args = expNode.GetChild("lambda_function_signature").GetDescendant("anonymous_function_parameter_list_opt"); if (args == null) { var child = expNode.GetChild("lambda_function_signature"); arglist.Add(Expression.Parameter( child.GetClrType() ?? _paramTypes.FirstOrDefault() ?? typeof(int), child.GetChild("Identifier").GetValue())); } else { for (int i = 0; i < args.ChildNodes.Count; ++i) { var child = args.ChildNodes[i]; arglist.Add(Expression.Parameter( child.GetClrType() ?? _paramTypes.Skip(i).FirstOrDefault() ?? typeof(int), child.GetChild("Identifier").GetValue())); } } EnterScope(arglist.ToArray()); var expression = expNode.GetChild("anonymous_function_body"); var body = ProcessExpression(expression); LeaveScope(); return Expression.Lambda(body, arglist); }private Expression ProcessLambdaExpression(ParseTreeNode expNode) { List<ParameterExpression> arglist = new List<ParameterExpression>(); var args = expNode.GetChild("lambda_function_signature").GetDescendant("anonymous_function_parameter_list_opt"); if (args == null) { var child = expNode.GetChild("lambda_function_signature"); arglist.Add(Expression.Parameter( child.GetClrType() ?? _paramTypes.FirstOrDefault() ?? typeof(int), child.GetChild("Identifier").GetValue())); } else { for (int i = 0; i < args.ChildNodes.Count; ++i) { var child = args.ChildNodes[i]; arglist.Add(Expression.Parameter( child.GetClrType() ?? _paramTypes.Skip(i).FirstOrDefault() ?? typeof(int), child.GetChild("Identifier").GetValue())); } } EnterScope(arglist.ToArray()); var expression = expNode.GetChild("anonymous_function_body"); var body = ProcessExpression(expression); LeaveScope(); return Expression.Lambda(body, arglist); }
[code]解析的时候虽然分2种无惨和有参的情况,但具体解析逻辑是一致的。由于我们解析的只是一串代表lambda表达式的字符串,如果参数没自带类型(大多数情况下没人写lambda会给参数加类型声明)没法根据上下文自动识别出,所以还需要一种机制能帮我们来识别出参数类型。
view plaincopy to clipboardprint?
private List<Type> _paramTypes = new List<Type>();
public Expression<TDelegate> Parse<TDelegate>(string expression)
{
_paramTypes.Clear();
var genericParameters = typeof(TDelegate).GetGenericTypeDefinition().GetGenericArguments();
var realArguments = typeof(TDelegate).GetGenericArguments();
_paramTypes.AddRange(
realArguments.Where((a, i) =>
genericParameters.First(p => p.GenericParameterPosition == i).
GenericParameterAttributes.HasFlag(GenericParameterAttributes.Contravariant)));
var exp = Parse(expression);
var genericlambda = exp as Expression<TDelegate>;
if (genericlambda != null)
return genericlambda;
var lambda = exp as LambdaExpression;
if (lambda != null)
{
return Expression.Lambda<TDelegate>(lambda.Body, lambda.Parameters);
}
return Expression.Lambda<TDelegate>(exp);
}
view plaincopy to clipboardprint? private void EnterScope(ParameterExpression[] parameters) { if (_parameters.Count > 0) _parameters.Push(_parameters.Peek().Union(parameters)); else _parameters.Push(parameters); } private void LeaveScope() { _parameters.Pop(); }private void EnterScope(ParameterExpression[] parameters) { if (_parameters.Count > 0) _parameters.Push(_parameters.Peek().Union(parameters)); else _parameters.Push(parameters); } private void LeaveScope() { _parameters.Pop(); }
[code]每次进入一个lambda的时候都会调用EnterScope把当前参数和前一个解析栈的参数放入,这样就保证每次进入一个表达式_parameters的栈顶都包含了当前及父辈们所有的参数。
到此为止,我们顺利的解决了表达式的参数传递问题,接下来说一下如何解决闭包类型变量的传递问题。
先看一个最简单的lambda:
int a = 2;
Func<int> f = () => a;
这个a是到了表达式范围外的变量,它没有定义在表达式内,所以不管我们解析的多完美对于这个a是没法解析的,而这个a就是所谓的闭包(clousre)了。观测C#编译器的做法是先在编译时插入一个类名后缀为DisplayClass的类,这个类的成员就是int a,类似于:
class <>c__DisplayClass { public int a { get; set; } }
然后把表达式所有对a的引用都改为对<>c__DisplayClass1.a的引用(要说为什么会用<>来做名字,可能是因为C#里无法使用<>作为类型名不会和用户定义的产生冲突,当然如果用户emit这种名字还是会冲突的)这样等于是把局部变量转换为一个可访问的内部变量,我们可以简单的模拟这种行为而不必要用如此大费周章:
view plaincopy to clipboardprint?
private Dictionary<string, object> _knownVariables = new Dictionary<string, object>();
public ExpressionParser With(Expression<Func<object>> variable)
{
var name = variable.Body.ToReadableString();
var @var = variable.Compile()();
return With(name, @var);
}
public ExpressionParser With(string name, object variable)
{
var parser = new ExpressionParser();
parser._knownVariables.Add(name, variable);
knownTypes.TryAdd(variable.GetType().Name, variable.GetType());
return parser;
}
private Dictionary<string, object> _knownVariables = new Dictionary<string, object>(); public ExpressionParser With(Expression<Func<object>> variable) { var name = variable.Body.ToReadableString(); var @var = variable.Compile()(); return With(name, @var); } public ExpressionParser With(string name, object variable) { var parser = new ExpressionParser(); parser._knownVariables.Add(name, variable); knownTypes.TryAdd(variable.GetType().Name, variable.GetType()); return parser; }
通过简单的
parser.With(()=>a).Parse("()=>a")
就完成了,甚至还能改名:
parser.With("b", a).Parse("()=>b")
其实就是个注册操作,这可以说是解析只有表达式而得不到其他相关上下文时的无奈之举了。
相关文章推荐
- lambda表达式的解析(五) Lambda表达式与闭包类型
- (转)lambda表达式的解析(三) 类型转换表达式
- Java 8 动态类型语言Lambda表达式实现原理解析
- lambda表达式的解析(三) 类型转换表达式
- (转)lambda表达式的解析(二) 常量表达式
- C++11 lambda 表达式解析
- 委托、Lambda表达式、事件系列05,Action委托与闭包
- C++11 lambda 表达式解析
- 浅谈Java 7的闭包与Lambda表达式之优劣
- Java SE8 Lambda 基础入门---lambda表达式的类型是什么
- 清理(委托类型实例)事件处理(实例)的函数及Lambda表达式
- lambda表达式 + 匿名函数 + 函数式编程 + 闭包 + 装饰器
- 无法将lambda表达式转换为类型“System.Delegate”
- 深入解析Python中的lambda表达式的用法
- (转)lambda表达式的解析(四) 运算符表达式
- C++11 lambda 表达式解析
- C++11 lambda 表达式解析
- 将Lambda表达式作为参数传递并解析——在构造函数参数列表中使用Lambda表达式(C#)
- C++11 lambda 表达式解析
- 无法将 lambda 表达式 转换为类型“System.Delegate”,因为它不是委托类型