我正在看MvcContrib网格组件,我很着迷,但同时被Grid syntax中使用的句法技巧击退:
.Attributes(style => "width:100%")
上面的语法将生成的HTML的style属性设置为 width:100%
. 现在,如果你注意,'style'没有指定,是从表达式中参数的 name 推断出来的!我不得不深入研究这个问题,并找到'magic'发生的地方:
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 回答
这是表达式树的一个好处 - 可以检查代码本身以获取额外信息 . 这就是如何将
.Where(e => e.Name == "Jamie")
转换为等效的SQL Where子句 . 这是表达树的巧妙使用,但我希望它不会比这更进一步 . 任何更复杂的东西都可能比它希望取代的代码更难,所以我怀疑它会自我限制 .欢迎来到Rails Land :)
只要你知道发生了什么,它就没有什么不妥 . (当没有记录这种事情时,存在问题) .
整个Rails框架 Build 在约定优于配置的基础上 . 以某种方式命名事物可以将您锁定到他们正在使用的约定中,并且您可以免费获得大量功能 . 遵循命名约定可以让您更快地到达目的地 . 整个过程非常出色 .
另一个我见过这样的技巧的地方是Moq中的方法调用断言 . 你传入一个lambda,但lambda永远不会被执行 . 他们只是使用表达式来确保方法调用发生,如果没有则抛出异常 .
所有这些关于“可怕”的咆哮都是一群长期反复思考的c#家伙(而且我是一名长期的C#程序员,并且仍然是该语言的忠实粉丝) . 这种语法没什么可怕的 . 它只是试图使语法看起来更像你想要表达的内容 . 语法中的“噪音”越少,程序员就越容易理解它 . 减少一行代码中的噪声只会有所帮助,但让它在越来越多的代码中积累,结果证明是一个重要的好处 .
这是作者试图争取DSL给你的同样的好处 - 当代码只是“看起来像”你想说的时候,你已经到了一个神奇的地方 . 您可以讨论这是否适用于互操作,或者它是否比匿名方法更好,以证明某些“复杂性”成本 . 足够公平......所以在你的项目中你应该正确选择是否使用这种语法 . 但仍然......这是一个程序员做一些聪明的尝试,在一天结束时,我们都在尝试做(无论我们是否意识到) . 而我们都想做的是:“告诉计算机我们希望用尽可能接近我们想要做什么的语言做什么 . ”
以与我们内部思考相同的方式接近向计算机表达我们的指令是使软件更易于维护和更准确的关键 .
编辑:我曾说过“使软件更易于维护和更准确的关键”,这是一种疯狂的过分夸张的独特性 . 我把它变成了“一把钥匙” .
我正处于“语法上的光彩”阵营,如果他们清楚地记录下来,看起来很酷,它几乎没有问题!
他们都 . 这是lambda表达式 AND 语法亮点的滥用 .
我觉得奇怪的不是因为这个名字,而是因为 lambda is unnecessary ;它可以使用匿名类型并且更灵活:
这是在ASP.NET MVC的大部分中使用的模式(例如),并且other uses(caveat,如果名称是魔术值而不是调用者特定,请注意Ayende's thoughts)
只是想提出我的看法(我是MvcContrib网格组件的作者) .
这绝对是语言滥用 - 毫无疑问 . 但是,当你看到对
Attributes(style => "width:100%", @class => "foo")
的调用时,我不会真的认为它是反直觉的 .我认为_337570正在进行(它肯定不比匿名类型方法更差) . 从智力理论的角度来看,我同意这是非常不透明的 .
对于那些感兴趣的人,在MvcContrib中使用它的一些背景信息......
我将此作为个人偏好添加到网格中 - 我不喜欢使用匿名类型作为字典(使用带有“object”的参数与使用params Func [])和Dictionary集合初始化程序的参数一样不透明相当冗长(我也不喜欢详细的流畅接口,例如必须将多个调用链接到一个属性(“style”,“display:none”) . 属性(“class”,“foo”)等)
如果C#对字典文字的语法不那么详细,那么我就不会在网格组件中包含这种语法了 .
我还想指出在MvcContrib中使用它是完全可选的 - 这些是包含带有IDictionary的重载的扩展方法 . 我认为重要的是,如果您提供这样的方法,您还应该支持更“正常”的方法,例如与其他语言互操作 .
另外,有人提到了'reflection overhead',我只想指出这种方法确实没有太大的开销 - 没有涉及运行时反射或表达式编译(参见http://blog.bittercoder.com/PermaLink,guid,206e64d1-29ae-4362-874b-83f5b103727f.aspx) .
我会比较喜欢
它更加明确和标准,并且使用lambdas没有获得任何东西 .
以下是什么问题:
这种互操作性很差 . 例如,考虑这个C# - F#示例
C#:
F#:
结果:
打印“arg”(不是“yadda”) .
因此,库设计者应该避免这种“滥用”,或者至少提供“标准”重载(例如,将字符串名称作为额外参数),如果他们希望在.Net语言之间具有良好的互操作性 .
如果方法(func)名称被很好地选择,那么这是避免维护麻烦的一种很好的方法(即:添加新的func,但忘记将其添加到函数参数映射列表中) . 当然,您需要对其进行大量记录,并且最好从该类中的函数的文档中自动生成参数的文档...
代码非常聪明,但它可能会导致更多问题解决 .
正如您所指出的,现在参数名称(样式)和HTML属性之间存在模糊的依赖关系 . 没有编译时间检查 . 如果参数名称输入错误,页面可能不会有运行时错误消息,但更难找到逻辑错误(没有错误,但行为不正确) .
更好的解决方案是拥有一个可以在编译时检查的数据成员 . 所以不是这样的:
编译器可以检查具有Style属性的代码:
甚至:
这对代码的作者来说更有用,但是这种方法利用了C#强大的类型检查功能,这有助于防止错误首先进入代码 .
在我看来,这是对lambdas的滥用 .
至于语法上的光彩,我发现
style=>"width:100%"
简直令人困惑 . 特别是因为=>
而不是=
这是一个有趣的方法 . 如果您将表达式的右侧约束为常量,那么您可以实现使用
我认为这是你真正想要的而不是委托(你使用lambda获取双方的名字)见下面的天真实现:
这甚至可以解决线程中前面提到的跨语言互操作问题 .
实际上它看起来像Ruby =),至少对我来说,为后来的动态“查找”使用静态资源不适合api设计考虑因素,希望这个聪明的技巧在api中是可选的 .
我们可以从IDictionary继承(或不继承)并提供一个索引器,当你不需要添加一个键来设置一个值时,它就像一个php数组 . 它将是.net语义的有效使用,而不仅仅是c#,仍然需要文档 .
希望这可以帮助
不,这当然不常见 . 这是违反直觉的,没有办法只看代码来弄清楚它的作用 . 你必须知道如何使用它来理解它是如何使用的 .
链接方法不是使用委托数组提供属性,而是更清晰,性能更好:
虽然输入的内容多一点,但它清晰直观 .
我几乎没有遇到过这种用法 . 我认为这是“不合适的”:)
这不是一种常用的使用方式,它与一般惯例不一致 . 这种语法当然有利有弊:
Cons
代码不直观(通常的约定不同)
它往往很脆弱(重命名参数会破坏功能) .
测试起来有点困难(伪造API需要在测试中使用反射) .
如果强烈使用表达式,由于需要分析参数而不仅仅是值(反射成本),它会变慢
Pros
Bottom line - 在公共API设计中,我会选择更明确的方式 .
恕我直言,这是一个很酷的方式 . 我们都喜欢这样一个事实,即命名一个类Controller会使它成为MVC中的控制器吗?因此,有些情况下命名很重要 .
意图也是这里很清楚 . 很容易理解
.Attribute( book => "something")
将导致book="something"
而.Attribute( log => "something")
将导致log="something"
我想如果你把它当成一种惯例,那应该不是问题 . 我认为无论是什么让你编写更少的代码并使意图明显是一件好事 .
这是多个级别的 horrible . 不,这与Ruby不同 . 这是对C#和.Net的滥用 .
关于如何以更直接的方式执行此操作有很多建议:元组,匿名类型,流畅的界面等等 .
让它变得如此糟糕的原因在于它只是为了自己的利益而着想:
.Attributes(Function(style) "width:100%")
它完全反直觉,intellisense将无法帮助确定如何传递内容 .
它不必要地效率低下 .
没有人会知道如何维护它 .
参数属性的类型是什么,是
Func<object,string>
?这个意图是如何揭示的 . 您的intellisense文档会说什么,"Please disregard all values of object"我认为你有这种反感的感觉是完全合理的 .
我可以用它来拼写短语吗?
magic lambda(n):一个lambda函数,仅用于替换魔术字符串 .
我认为这并不比“魔术弦”好 . 对于这个,我不太喜欢匿名类型 . 它需要更好的和强类型的方法 .