如何创建一个接受多个 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 来获取多个参数,而不是只使用一个来返回具有必要字段的新匿名对象? &lt;%: Html._RouteButton("details", "Health", "SystemDetails", m=&gt; new id= m.Id, status = m.Status)%&gt; @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 显然都不是最好的界限,所以我不知道你的意思;请明确说明”。

【讨论】:

我可以看到失败的模式,但似乎类型是 Func 而不是字符串、整数或小数。为什么.Net会进行比方法签名更严格的验证? @Nathaniel:R 不是类型。 R 是一个占位符,必须用类型填充。 (与参数不是值一样,参数是必须提供值的位置,称为参数。)编译器告诉您它无法确定您打算用什么类型替换 R . @Nathaniel:这是编译时间限制,而不是运行时错误。它与编组无关。这样看。当您有一个方法 M&lt;T&gt; 并调用它时,您需要提供一个类型参数。您要求说出M&lt;int&gt;,就像您在制作新列表时要求说出List&lt;int&gt;一样。它必须是一个的列表。该方法必须是某物的方法。现在,出于礼貌,如果您省略&lt;int&gt;,编译器将尝试猜测您的意思。如果编译器无法猜测,那么它会告诉你。 但是您必须说出方法类型参数是什么,或者提供编译器可以猜测的足够信息你想说什么。你什么都没有做,所以你得到一个编译时错误。 @Nathaniel:仍然是编译时错误,而不是运行时错误。【参考方案2】:

Eric Lippert 的回答解释了问题所在。我将添加如何实际解决它:您很可能不需要 lambda 来返回属性的类型,您只想获取表达式树来检查属性。因此,您可以做的是将类型从Func&lt;TModel, TProperty&gt; 更改为Func&lt;TModel, object&gt;(并删除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 表达式作为参数的方法?的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 lambda 表达式作为模板参数?

第二十一课:函数:lambda表达式

通用 lambda 及其作为常量表达式的参数

读Java实战(第二版)笔记02_行为参数化Lambda表达式

新手入门,深入解析 python lambda表达式

java8:lambda级联表达式(Cascading)或柯里化(Currying)原理简化详解