反映参数名称:滥用C#lambda表达式还是语法亮度?

时间:2009-11-11 21:00:35

标签: c# asp.net-mvc lambda mvccontrib

我正在看MvcContrib网格组件,我很着迷,但同时被Grid syntax中使用的句法技巧击退了:

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

上面的语法将生成的HTML的style属性设置为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网格语法,我发现代码主动查找并根据我为变量选择的名称做出决定!

这是C#3.5 / 4.0社区和lambda表达爱好者的常见做法吗?或者是一个我不应该担心的流氓特立独行?

21 个答案:

答案 0 :(得分:154)

由于名称,我觉得这很奇怪,但因为 lambda是不必要的;它可以使用匿名类型并且更灵活:

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

这是在ASP.NET MVC的大部分内容中使用的模式(例如),并且有other uses(一个caveat,如果名称是魔术值,请注意Ayende's thoughts比来电专用)

答案 1 :(得分:146)

这种互操作性很差。例如,考虑这个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”)。

因此,库设计者应该避免这种“滥用”,或者至少提供“标准”重载(例如,将字符串名称作为额外参数),如果他们想要具有良好的互操作性。网络语言。

答案 2 :(得分:137)

只是想提一下(我是MvcContrib网格组件的作者)。

这绝对是语言滥用 - 毫无疑问。但是,当你看到Attributes(style => "width:100%", @class => "foo")的电话时,我真的不会认为这是违反直觉的 我认为这很明显是什么(它肯定不比匿名类型方法更糟)。从智能理论的角度来看,我同意这是非常不透明的。

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

我将此作为个人偏好添加到网格中 - 我不喜欢使用匿名类型作为词典(使用带有“object”的参数与使用params Func [])和词典的参数一样不透明集合初始化程序相当冗长(我也不喜欢详细的流畅接口,例如必须将多个调用链接到一个Attribute(“style”,“display:none”)。属性(“class”,“foo”)等)

如果C#的字典文字语法较为冗长,那么我就不会在网格组件中包含这种语法:)

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

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

答案 3 :(得分:48)

我更喜欢

Attributes.Add(string name, string value);

它更明确,更标准,使用lambdas没有任何好处。

答案 4 :(得分:46)

欢迎来到Rails Land:)

只要你知道发生了什么,它就没有什么问题。 (当这种事情没有很好地证明存在问题时)。

整个Rails框架都建立在约定优于配置的基础之上。以某种方式命名事物可以将您锁定到他们正在使用的约定中,并且您可以免费获得大量功能。遵循命名约定可以让您更快地到达目的地。整个过程非常出色。

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

答案 5 :(得分:42)

在多个级别上,这是可怕的。不,这与Ruby不同。这是对C#和.Net的滥用。

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

让它变得如此糟糕的原因在于它只是为了自己的利益而着想:

  • 当你需要从VB调用它时会发生什么?

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

  • 它完全反直觉,智能感知将无法帮助确定如何传递内容。

  • 它不必要地效率低下。

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

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

我认为你有这种反感的感觉是完全合理的。

答案 6 :(得分:40)

我正处于“语法上的光彩”阵营,如果他们清楚地记录下来,看起来很酷,它几乎没有问题!

答案 7 :(得分:37)

他们两个。它是lambda表达式 AND 语法亮度的滥用。

答案 8 :(得分:21)

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

这不是一种常用的使用方式,它与一般惯例不一致。这种语法当然有利有弊:

<强>缺点

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

<强>赞成

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

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

答案 9 :(得分:18)

不,这当然不常见。这是违反直觉的,没有办法只看代码来弄清楚它的作用。你必须知道如何使用它来理解它是如何被使用的。

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

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

虽然输入的内容多一点,但它清晰直观。

答案 10 :(得分:17)

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

这是作者试图争取DSL给你的同样好处 - 当代码只是“看起来像”你想说的时候,你已经到了一个神奇的地方。您可以讨论这是否适用于互操作,或者它是否比匿名方法更好,以证明某些“复杂性”成本。足够公平......所以在你的项目中你应该正确选择是否使用这种语法。但仍然......这是一个程序员做一些聪明的尝试,在一天结束时,我们都在尝试做(无论我们是否意识到)。而我们所有人都想做的是:“告诉计算机我们希望用尽可能接近我们想要做什么的语言做什么。”

更接近于以与我们内部思考相同的方式向计算机表达我们的指示,这是使软件更易于维护和更准确的关键。

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

答案 11 :(得分:17)

以下内容有什么问题:

html.Attributes["style"] = "width:100%";

答案 12 :(得分:17)

我可以用它来拼写短语吗?

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

答案 13 :(得分:12)

这是表达式树的一个好处 - 可以检查代码本身以获取额外信息。这就是.Where(e => e.Name == "Jamie")可以转换为等效的SQL Where子句的方式。这是表达树的巧妙使用,但我希望它不会比这更进一步。任何更复杂的东西都可能比它希望取代的代码更难,所以我怀疑它会自我限制。

答案 14 :(得分:7)

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

实现
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;
}

这甚至可能解决了之前在线程中提到的跨语言互操作问题。

答案 15 :(得分:6)

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

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

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

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

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

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

甚至:

.Attributes.Style.Width.Percent = 100;

这对代码的作者来说更有用,但是这种方法利用了C#强大的类型检查功能,这有助于防止错误首先进入代码。

答案 16 :(得分:5)

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

我们可以继承IDictionary(或者不是)并提供一个索引器,当你不需要添加一个键来设置一个值时,它就像一个php数组。它将是.net语义的有效使用,不仅仅是c#,还需要文档。

希望这会有所帮助

答案 17 :(得分:5)

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

此处的意图非常明确。很容易理解,.Attribute( book => "something")会导致book="something".Attribute( log => "something")会导致log="something"

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

答案 18 :(得分:4)

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

至于语法上的光彩,我发现style=>"width:100%"简直令人困惑。特别是因为=>而不是=

答案 19 :(得分:3)

如果方法(func)名称被很好地选择,那么这是避免维护麻烦的一种很好的方法(即:添加新的func,但忘记将其添加到函数参数映射列表中)。当然,你需要大量记录它,你最好自动生成该类函数文档中的参数文档......

答案 20 :(得分:1)

我认为这并不比“魔术字符串”好。对于这个,我不太喜欢匿名类型。它需要一个更好的&amp;强类型方法。