如何创建一个接受多个 lambda 表达式作为参数的方法?
Posted
技术标签:
【中文标题】如何创建一个接受多个 lambda 表达式作为参数的方法?【英文标题】:How do I make a method that accepts multiple lambda expressions as parameters? 【发布时间】:2013-01-30 13:37:36 【问题描述】:我正在尝试制作自己的扩展方法,该方法可以采用任意数量的 lambda 表达式,但每当我添加多个表达式时,它似乎都会窒息。
方法如下:
public static MvchtmlString _RouteButton<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, string label, string controller, string action, params Expression<Func<TModel, TProperty>>[] parameters)
var test = parameters;
return MvcHtmlString.Empty;
这是成功调用它的标记:
<%: Html._RouteButton("details", "Health", "SystemDetails", m=>m.Id)%>
这是错误的标记:
<%: Html._RouteButton("details", "Health", "SystemDetails", m=>m.Id, m=>m.Status)%>
这是错误:
方法的类型参数不能从用法中推断出来。尝试 明确指定类型参数
感谢任何帮助。谢谢!
【问题讨论】:
Id和Status有哪些类型? 您是否可以使用多个 Lambda 来获取多个参数,而不是只使用一个来返回具有必要字段的新匿名对象?<%: Html._RouteButton("details", "Health", "SystemDetails", m=> new id= m.Id, status = m.Status)%>
@ShaneA 你不能这样做,因为如果涉及匿名类型,你就不能为参数定义类型。
Shane,我实际上有一个覆盖,但为了简单起见,我想传递 lambda 表达式。
【参考方案1】:
让我们简化一下:
using System;
class P
static void M<R>(params Func<R>[] p)
static void N(int i, string s, decimal m)
M(()=>i, ()=>s); // fails
M(()=>i, ()=>s.Length); // succeeds
M(()=>i, ()=>m); // succeeds
现在您的程序失败的原因很清楚了吗?
在我的程序中,每次调用都尝试推断 R。在第一次调用中,R 被推断为 int 和 string,因此推断失败,因为没有类型既是 int 又是 string。在第二个中,R 被推断为 int 并且......再次为 int !成功是因为 int 匹配所有边界。第三种,R被推断为int和decimal,成功了,因为每一个int都可以隐式转换为decimal,所以decimal是一个很好的推断。
Id 和 Status 可能是类型不兼容的属性。如果您想这样做,那么推断的类型之一必须是最佳类型。
请注意,C# 从不说“哦,我看到你推断出狗、猫和鱼的界限,所以我猜你的意思是动物”。 C# 宁愿说“Dog、Cat 或 Fish 显然都不是最好的界限,所以我不知道你的意思;请明确说明”。
【讨论】:
我可以看到失败的模式,但似乎类型是 FuncM<T>
并调用它时,您需要提供一个类型参数。您要求说出M<int>
,就像您在制作新列表时要求说出List<int>
一样。它必须是一个的列表。该方法必须是某物的方法。现在,出于礼貌,如果您省略<int>
,编译器将尝试猜测您的意思。如果编译器无法猜测,那么它会告诉你。
但是您必须说出方法类型参数是什么,或者提供编译器可以猜测的足够信息你想说什么。你什么都没有做,所以你得到一个编译时错误。
@Nathaniel:仍然是编译时错误,而不是运行时错误。【参考方案2】:
Eric Lippert 的回答解释了问题所在。我将添加如何实际解决它:您很可能不需要 lambda 来返回属性的类型,您只想获取表达式树来检查属性。因此,您可以做的是将类型从Func<TModel, TProperty>
更改为Func<TModel, object>
(并删除TProperty
类型参数)。由于 C# 中的所有普通类型都可以隐式转换为 object
,因此您的代码将在此更改后编译并正常运行
【讨论】:
这正是我最终得到的解决方案。我必须再等几个小时才能获准发布,但我会在那时发布。【参考方案3】:我想把这个放在网站上,以防有人尝试类似的事情。
代码构建了一个路由表单。为此,它需要属性的名称及其值。
public static void _RouteButton<TModel>(this HtmlHelper<TModel> htmlHelper, string text, string controller, string action, params Expression<Func<TModel, object>>[] parameters)
using (htmlHelper.BeginRouteForm("Default", new controller = controller, action = action ))
foreach (Expression<Func<TModel, object>> p in parameters)
MemberExpression me;
switch (p.Body.NodeType)
case ExpressionType.Convert:
case ExpressionType.ConvertChecked:
var ue = p.Body as UnaryExpression;
me = ((ue != null) ? ue.Operand : null) as MemberExpression;
break;
default:
me = p.Body as MemberExpression;
break;
string name = me.Member.Name;
string value = p.Compile()(htmlHelper.ViewData.Model).ToString();
HttpContext.Current.Response.Write(htmlHelper.Hidden(name, value).ToHtmlString());
HttpContext.Current.Response.Write("<input type='submit' value='" + text + "' />");
【讨论】:
【参考方案4】:@eric-lippert 以您的示例解释了“为什么它不起作用”。每个表达式中传递的属性类型不一样。
将扩展方法的签名更改为仅指定 TModel,因为泛型将有助于解决问题。另外,为表达式函数指定object代替TProperty。
扩展方法
public static MvcHtmlString _RouteButton<TModel>(this HtmlHelper<TModel> htmlHelper,
params Expression<Func<TModel, object>>[] parameters)
var test = parameters;
return MvcHtmlString.Empty;
ASP/Razor
<%: Html._RouteButton(m=>m.Id, m=>m.Status)%>
@Html._RouteButton(m=>m.Id, m=>m.Status)
【讨论】:
以上是关于如何创建一个接受多个 lambda 表达式作为参数的方法?的主要内容,如果未能解决你的问题,请参考以下文章