433

我正在查看MvcContrib Grid 组件,我很着迷,但同时又被Grid 语法中使用的句法技巧所排斥:

.Attributes(style => "width:100%")

上面的语法将生成的 HTML 的样式属性设置为width:100%. 现在,如果您注意,没有指定“样式”。它是从表达式中的参数名称推导出来的!我不得不深入研究并发现“魔术”发生的地方:

Hash(params Func<object, TValue>[] hash)
{
    foreach (var func in hash)
    {
        Add(func.Method.GetParameters()[0].Name, func(null));
    }
}

所以确实,代码使用正式的、编译时的、参数名称来创建属性名称-值对的字典。由此产生的语法结构确实非常具有表现力,但同时也非常危险。

lambda 表达式的一般使用允许替换使用的名称而不会产生副作用。我在一本书中看到一个例子,它说collection.ForEach(book => Fire.Burn(book))我知道我可以在我的代码中编写collection.ForEach(log => Fire.Burn(log))这意味着同样的事情。但是突然之间有了 MvcContrib Grid 语法,我发现代码可以根据我为变量选择的名称主动查找和做出决定!

那么对于 C# 3.5/4.0 社区和 lambda 表达式爱好者来说,这是一种常见的做法吗?还是流氓是我不应该担心的特立独行?

4

21 回答 21

154

我觉得奇怪不是因为名字,而是因为lambda 是不必要的;它可以使用匿名类型并且更灵活:

.Attributes(new { style = "width:100%", @class="foo", blip=123 });

这是在大部分 ASP.NET MVC 中使用的模式(例如),并且有其他用途警告,如果名称是魔术值而不是特定于调用者,请注意Ayende 的想法)

于 2009-11-11T21:35:26.420 回答
148

这具有较差的互操作性。例如,考虑这个 C# - F# 示例

C#:

public class Class1
{
    public static void Foo(Func<object, string> f)
    {
        Console.WriteLine(f.Method.GetParameters()[0].Name);
    }
}

F#:

Class1.Foo(fun yadda -> "hello")

结果:

打印“arg”(不是“yadda”)。

因此,库设计者应该避免这些类型的“滥用”,或者至少提供一个“标准”重载(例如,将字符串名称作为额外参数),如果他们想要跨 .Net 语言进行良好的互操作。

于 2009-11-11T21:10:27.427 回答
138

只是想发表一下我的看法(我是 MvcContrib 网格组件的作者)。

这绝对是语言滥用——毫无疑问。但是,我不会真的认为它违反直觉 - 当您查看对我的调用时,Attributes(style => "width:100%", @class => "foo")
我认为发生了什么非常明显(它肯定不比匿名类型方法差)。从智能感知的角度来看,我同意它非常不透明。

对于那些感兴趣的人,一些关于它在 MvcContrib 中使用的背景信息......

我将此作为个人喜好添加到网格中 - 我不喜欢将匿名类型用作字典(具有采用“object”的参数与采用 params Func[] 的参数一样不透明)并且 Dictionary 集合初始值设定项是相当冗长(我也不喜欢冗长的流畅接口,例如必须将多个对 Attribute("style", "display:none").Attribute("class", "foo") 等的调用链接在一起)

如果 C# 对字典文字的语法不那么冗长,那么我就不会费心在网格组件中包含这个语法了 :)

我还想指出,在 MvcContrib 中使用 this 是完全可选的——这些是包装重载的扩展方法,这些重载取而代之的是 IDictionary。我认为重要的是,如果您提供这样的方法,您还应该支持更“正常”的方法,例如与其他语言的互操作。

此外,有人提到了“反射开销”,我只是想指出这种方法确实没有太多开销 - 不涉及运行时反射或表达式编译(参见http://blog.bittercoder.com /PermaLink,guid,206e64d1-29ae-4362-874b-83f5b103727f.aspx )。

于 2009-12-02T10:27:40.123 回答
49

我会比较喜欢

Attributes.Add(string name, string value);

它更加明确和标准,使用 lambdas 没有任何收获。

于 2009-11-11T21:06:11.400 回答
46

欢迎来到 Rails Land :)

只要你知道发生了什么,它真的没有什么问题。(当这种事情没有很好地记录时,就会出现问题)。

整个 Rails 框架建立在约定优于配置的思想之上。以某种方式命名事物会使您进入他们正在使用的约定,并且您可以免费获得很多功能。遵循命名约定可以让您更快地到达目的地。整个事情非常出色。

我看到类似技巧的另一个地方是 Moq 中的方法调用断言。你传入一个 lambda,但 lambda 永远不会被执行。他们只是使用表达式来确保方法调用发生,如果没有发生则抛出异常。

于 2009-11-11T21:09:07.040 回答
42

这在不止一个层面上是可怕的。不,这与 Ruby 完全不同。这是对 C# 和 .NET 的滥用。

有很多关于如何以更直接的方式做到这一点的建议:元组、匿名类型、流畅的接口等等。

让它如此糟糕的是,它只是为了自己的利益而幻想:

  • 当您需要从 Visual Basic 调用它时会发生什么?

    .Attributes(Function(style) "width:100%")

  • 这完全是违反直觉的,智能感知在弄清楚如何传递东西方面几乎没有帮助。

  • 这是不必要的低效。

  • 没有人会知道如何维护它。

  • 进入属性的参数类型是什么?是Func<object,string>吗?这种意图是如何揭示的?您的智能感知文档会说什么,“请忽略对象的所有值”?

我认为你完全有理由感到厌恶。

于 2009-11-11T23:48:43.063 回答
39

我在“语法辉煌”阵营中,如果他们清楚地记录它,并且看起来非常酷,那么它几乎没有问题!

于 2009-11-11T21:07:23.217 回答
37

两个都。这是对 lambda 表达式语法的滥用。

于 2009-12-02T08:21:19.033 回答
21

我几乎没有遇到过这种用法。我认为这是“不合适的”:)

这不是常用的使用方式,不符合一般约定。这种语法当然有利有弊:

缺点

  • 代码不直观(通常的约定不同)
  • 它往往很脆弱(重命名参数会破坏功能)。
  • 测试起来有点困难(伪造 API 需要在测试中使用反射)。
  • 如果大量使用表达式,由于需要分析参数而不仅仅是值(反射成本),它会变慢

优点

  • 开发人员调整到此语法后,它更具可读性。

底线- 在公共 API 设计中,我会选择更明确的方式。

于 2009-11-11T21:12:11.243 回答
19

不,这当然不是常见的做法。这是违反直觉的,没有办法只看代码就知道它做了什么。你必须知道它是如何被使用的,才能理解它是如何被使用的。

与使用委托数组提供属性不同,链接方法会更清晰,性能更好:

.Attribute("style", "width:100%;").Attribute("class", "test")

尽管这需要输入更多内容,但它清晰直观。

于 2009-11-11T21:13:12.410 回答
18

我可以用它来造一个短语吗?

魔术 lambda (n):一个 lambda 函数,仅用于替换魔术字符串。

于 2009-12-03T16:01:35.890 回答
17

以下有什么问题:

html.Attributes["style"] = "width:100%";
于 2009-12-07T12:00:52.340 回答
16

所有这些关于“可怕”的咆哮都是一群长期使用 c# 的人反应过度(我是一名长期的 C# 程序员,并且仍然是该语言的忠实粉丝)。这种语法没有什么可怕的。它只是试图使语法看起来更像您要表达的内容。语法中的“噪音”越少,程序员就越容易理解它。减少一行代码中的噪音只会有一点帮助,但是让它在越来越多的代码中积累起来,结果证明是一个巨大的好处。

这是作者试图争取 DSL 给您带来的相同好处的尝试——当代码“看起来像”您想要表达的内容时,您已经到达了一个神奇的地方。您可以争论这是否有利于互操作,或者它是否比匿名方法更好来证明一些“复杂性”成本是合理的。很公平……所以在您的项目中,您应该正确选择是否使用这种语法。但是……这是程序员的一次聪明尝试,最终我们都在尝试做的事情(无论我们是否意识到)。我们都在努力做的是:“用尽可能接近我们对它想要做什么的看法的语言告诉计算机我们想要它做什么。”

更接近于以我们内部认为的相同方式向计算机表达我们的指令是使软件更易于维护和更准确的关键。

编辑:我曾说过“使软件更易于维护和更准确的关键”,这是一种疯狂天真的夸大了的独角兽。我把它改成了“一把钥匙”。

于 2010-01-08T05:52:46.900 回答
12

这是表达式树的好处之一——人们可以检查代码本身以获得额外的信息。那就是如何.Where(e => e.Name == "Jamie")可以转换成等效的SQL Where 子句。这是对表达式树的巧妙使用,尽管我希望它不会比这更进一步。任何更复杂的东西都可能比它希望替换的代码更困难,所以我怀疑它会自我限制。

于 2009-11-11T21:06:30.510 回答
8

这是一种有趣的方法。如果您将表达式的右侧限制为常量,那么您可以使用

Expression<Func<object, string>>

我认为这是您真正想要的而不是委托(您正在使用 lambda 来获取双方的名称)请参见下面的幼稚实现:

public static IDictionary<string, string> Hash(params Expression<Func<object, string>>[] hash) {
    Dictionary<string, string> values = new Dictionary<string,string>();
    foreach (var func in hash) {
        values[func.Parameters[0].Name] = ((ConstantExpression)func.Body).Value.ToString();
    }
    return values;
}

这甚至可以解决线程前面提到的跨语言互操作问题。

于 2009-12-03T11:27:43.937 回答
6

该代码非常聪明,但它可能会导致它解决的更多问题。

正如您所指出的,现在参数名称(样式)和 HTML 属性之间存在一种模糊的依赖关系。不进行编译时检查。如果参数名称输入错误,页面可能不会有运行时错误消息,但更难找到逻辑错误(没有错误,但行为不正确)。

更好的解决方案是拥有一个可以在编译时检查的数据成员。所以代替这个:

.Attributes(style => "width:100%");

编译器可以检查带有 Style 属性的代码:

.Attributes.Style = "width:100%";

甚至:

.Attributes.Style.Width.Percent = 100;

这对代码的作者来说是更多的工作,但这种方法利用了 C# 强大的类型检查能力,这有助于从一开始就防止错误进入代码。

于 2009-11-11T21:23:00.237 回答
5

确实它看起来像 Ruby =),至少对我来说,使用静态资源进行以后的动态“查找”不适合 api 设计考虑,希望这个聪明的技巧在那个 api 中是可选的。

当您不需要添加键来设置值时,我们可以从 IDictionary 继承(或不继承)并提供一个行为类似于 php 数组的索引器。这将是对 .net 语义的有效使用,而不仅仅是 c#,并且仍然需要文档。

希望这可以帮助

于 2009-11-11T22:30:34.603 回答
4

在我看来,这是对 lambda 的滥用。

至于语法上的出色,我觉得style=>"width:100%"很混乱。特别是=>因为=

于 2009-11-11T21:20:01.853 回答
4

恕我直言,这是一种很酷的方式。我们都喜欢这样一个事实,即命名一个类 Controller 将使它成为 MVC 中的控制器,对吧?因此,在某些情况下,命名确实很重要。

这里的意图也很明确。很容易理解这.Attribute( book => "something")将导致book="something"并将.Attribute( log => "something")导致log="something"

我想如果你把它当作一个约定来对待应该不是问题。我认为无论什么让你编写更少的代码并使意图变得明显是一件好事。

于 2009-12-02T16:43:43.987 回答
3

如果方法(func)名称选择得当,那么这是避免维护问题的绝妙方法(即:添加新的 func,但忘记将其添加到函数参数映射列表中)。当然,您需要大量记录它,并且最好从该类中的函数的文档中自动生成参数的文档...

于 2009-11-11T21:18:30.450 回答
1

我认为这并不比“魔术弦”更好。为此,我也不太喜欢匿名类型。它需要更好的强类型方法。

于 2009-12-07T11:43:45.287 回答