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

Rem*_*anu 425 c# asp.net-mvc lambda mvccontrib

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

.Attributes(style => "width:100%")
Run Code Online (Sandbox Code Playgroud)

上面的语法将生成的HTML的style属性设置为width:100%.现在如果你注意,"风格"没有指定,是从表达式中参数的名称推断出来的!我不得不深入研究这个并发现"神奇"发生的地方:

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

实际上,代码使用正式的编译时,参数名来创建属性名称 - 值对的字典.结果语法结构确实非常具有表现力,但同时也非常危险.lambda表达式的一般用法允许替换使用的名称而没有副作用.我在一本书中看到一个例子,说collection.ForEach(book => Fire.Burn(book))我知道我可以用我的代码编写collection.ForEach(log => Fire.Burn(log)),这意味着同样的事情.但是使用MvcContrib网格语法突然间,我发现代码主动查找并根据我为变量选择的名称进行分解!

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

Mar*_*ell 154

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

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

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

  • 在编写代码之后,我认为问题不是可读性**.我认为真正的问题是代码的可学习性.你的intellisense说*.Attributes(object obj)*时你会想到什么?你必须去阅读文档(没有人想做)因为你不知道传递给方法的内容.我认为这不比问题中的例子好. (26认同)
  • 我很想看到Eric Lippert对此的反应.因为*它在FRAMEWORK代码*中.它同样可怕. (7认同)
  • 我喜欢没有人真正回答这个问题,而是人们提供了"这是更好"的论点.:p是否滥用? (5认同)
  • 这也有互操作问题; 并非所有语言都支持动态创建匿名类型. (4认同)
  • @Arnis - 为什么更灵活:它不依赖于隐含的参数名称,*可能*(不引用我)导致一些lambda实现(其他语言)的问题 - 但您也可以使用具有已定义属性的常规对象.例如,你可以有一个带有预期属性的`HtmlAttributes`类(用于intellisense),只需忽略那些带有'null`值的类...... (2认同)

Bri*_*ian 146

这有很差的互操作性.例如,考虑这个C# - F#示例

C#:

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

F#:

Class1.Foo(fun yadda -> "hello")
Run Code Online (Sandbox Code Playgroud)

结果:

印有"arg"(不是"yadda").

因此,库设计者应该避免这种"滥用",或者至少提供"标准"重载(例如,将字符串名称作为额外参数),如果他们想要在.Net语言之间进行良好的互操作.

  • 我不喜欢非互操作性作为不做某事的理由.如果需要互操作性,那么如果没有,那么为什么要担心呢?这是YAGNI恕我直言. (31认同)
  • 我同意你不必兼容CLS,但是如果你正在编写一个库或控件(并且启动它的片段来自网格,是吗?)似乎是一个好主意.否则,你只是限制您的受众/客户群 (25认同)
  • @jfar:在.NET CLR中,陆地互操作性有一个全新的范围,因为*any*编译器中生成的程序集应该从任何*其他*语言中使用. (15认同)
  • 你没有.这种策略简直是不可移植的.(如果它有帮助,作为一个例子另一种方式,F#能够重载仅在返回类型上不同的方法(类型推断可以做到这一点).这可以在CLR中表达.但是F#不允许它,主要是因为如果你这样做了,这些API无法从C#中调用.)当谈到互操作时,总是会在"边缘"功能上进行权衡,看看你得到的好处与你交易的互操作性. (11认同)
  • 也许值得更改:Func <object,string>到Expression << Func <object,string >>如果你将表达式的右边限制为常量,你可以有一个实现来做到这一点:public static IDictionary <string,string> Hash(params Expression <Func <object,string >> [] hash){Dictionary <string,string> values = new Dictionary <string,string>(); foreach(散列中的var func){values [func.Parameters [0] .Name] =(string)((ConstantExpression)func.Body).Value; 返回值; } (4认同)
  • @Remus Rusanu:只要你没有声称自己是[CLSCompliant],我认为做不可互操作的事情是可以的. (2认同)
  • 互操作关注+1.它是众多中的唯一之一. (2认同)

Jer*_*ner 137

只是想提出我的看法(我是MvcContrib网格组件的作者).

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

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

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

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

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

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

  • +1表示通过*optional*扩展方法添加接口.非C#用户(以及因语言滥用而冒犯的任何人)可以简单地避免使用它. (22认同)
  • 它在Intellisense中不比匿名对象更不透明. (4认同)
  • 我还尝试在我的博客上更深入地解决这里提出的一些问题:http://www.jeremyskinner.co.uk/2009/12/02/lambda-abuse-the-mvccontrib-hash/ (2认同)

Not*_*Dan 49

我会比较喜欢

Attributes.Add(string name, string value);
Run Code Online (Sandbox Code Playgroud)

它更加明确和标准,并且使用lambdas没有获得任何东西.

  • @Jamie:尝试使C#代码看起来像HTML代码将是设计决策的一个不好的理由.对于完全不同的目的,它们是完全不同的语言,它们看起来不一样. (27认同)
  • 是吗?`html.Attributes.Add("style","width:100%");`的读取效果不如`style ="width:100%"`(生成的实际html),而`style =>" width:100%"`非常接近生成的HTML中的样子. (20认同)
  • 在不牺牲"美感"的情况下,也可以使用匿名对象?.Attributes(new {id ="foo",@ class ="bar",style ="width:100%"})?? (17认同)
  • @Guffa为什么它会成为设计决策的坏理由?他们为什么看起来不一样?根据这个推理,他们*有意*看起来有什么不同?我不是说你错了,我只是说你可能想要更全面地阐述你的观点. (10认同)
  • 它们的语法允许使用.Attributes(id =>'foo',@ class =>'bar',style =>'width:100%')等技巧.函数签名对可变数量的args使用params语法:Attributes(params Func <object,object> [] args).这是非常强大的,但它花了我很长时间*来理解wtf它. (6认同)
  • @Stuart:使输出HTML的代码看起来像HTML就像制作打印条形码的代码看起来像条形码一样......编写代码的方式不应受输出的影响.故意使代码看起来不同并不适用于此,但在ASP代码中,我故意夸大了VBScript和Javascript之间的差异,使它们易于识别. (3认同)
  • @Guffa,根据这个推理,你永远不会使用像ASP.NET,ASP.NET MVC,JSP或任何类似的Web技术.它们存在的原因是让您的视图代码看起来尽可能接近html.在出现任何servlet之前我们已经有了servlet,而servlet只是一种从代码生成文本html的方法.世界想要的东西看起来更像是正在生成的输出,显然你也提到了ASP. (2认同)

Jas*_*yon 46

欢迎来到Rails Land :)

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

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

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

  • 我无法弄清楚为什么这对我来说并不奇怪,然后我想起了Rails.:d (5认同)
  • 我有点犹豫,但我同意.除了反射开销之外,使用Add()中的字符串与使用lambda参数名称之间没有显着差异.至少我能想到的.你可以搞砸它并输入"sytle"而不注意两种方式. (3认同)

Sam*_*ron 42

这在多个层面上都很糟糕.不,这与Ruby不同.这是对C#和.Net的滥用.

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

让它变得如此糟糕的原因在于它只是想要自己的好处:

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

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

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

  • 它的效率不必要地低效.

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

  • 进入属性的参数类型是什么Func<object,string>?是吗?这个意图是如何揭示的.您的intellisense文档会说什么,"请忽略对象的所有值"

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

  • 我会说 - 这完全是直观的.:) (4认同)
  • 你说它不像Ruby.但它很像Ruby的语法,用于指定哈希表的键和值. (4认同)
  • 在alpha转换下破解的代码!Yey! (3认同)
  • @Charlie,从语法上讲它看起来很相似,从语义上讲它是不同的. (3认同)

Bli*_*ndy 40

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

  • 阿门,兄弟.阿门(需要满足最小长度评论的第二个阿门:) (10认同)

Arn*_*psa 37

他们都.它是lambda表达式语法亮度的滥用.

  • 那么这是对lambda表达式的一种精彩的语法滥用吗?我想我同意:) (16认同)

Eli*_*sha 21

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

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

缺点

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

优点

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

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

  • @Elisha - 你的优点和缺点是逆转的.至少我希望你不是说专业人士的代码"不直观".;-) (2认同)

Guf*_*ffa 18

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

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

.Attribute("style", "width:100%;").Attribute("class", "test")
Run Code Online (Sandbox Code Playgroud)

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

  • 我猜是使用`.Attribute("style","width:100%")`给了我`style ="width:100%"`,但是据我所知,它可以给我`foooooo`.我没有看到差异. (11认同)
  • 真?当我查看它时,我确切地知道了代码片段的内容.除非你非常严格,否则不是那么迟钝.可以给出关于字符串连接的重载+的相同论点,并且我们应该总是使用Concat()方法. (6认同)
  • "根据使用的值进行猜测"始终是您在查看代码时所执行的操作.如果遇到对stream.close()的调用,则假定它关闭了一个流,但它可能会做一些完全不同的事情. (5认同)
  • @Stuart:不,你不确切地知道,你只是根据使用的值猜测.任何人都可以猜测,但猜测并不是理解代码的好方法. (4认同)

cit*_*att 17

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

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


Tom*_*ier 17

以下是什么问题:

html.Attributes["style"] = "width:100%";
Run Code Online (Sandbox Code Playgroud)


Cha*_*ers 17

All this ranting about "horridness" is a bunch of long-time c# guys overreacting (and I'm a long-time C# programmer and still a very big fan of the language). There's nothing horrible about this syntax. It is merely an attempt to make the syntax look more like what you're trying to express. The less "noise" there is in the syntax for something, the easier the programmer can understand it. Decreasing the noise in one line of code only helps a little, but let that build up across more and more code, and it turns out to be a substantial benefit.

This is an attempt by the author to strive for the same benefits that DSL's give you -- when the code just "looks like" what you're trying to say, you've reached a magical place. You can debate whether this is good for interop, or whether it is enough nicer than anonymous methods to justify some of the "complexity" cost. Fair enough ... so in your project you should make the right choice of whether to use this kind of syntax. But still ... this is a clever attempt by a programmer to do what, at the end of the day, we're all trying to do (whether we realize it or not). And what we're all trying to do, is this: "Tell the computer what we want it to do in language that is as close as possible to how we think about what want it to do."

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

编辑:我曾说过"使软件更易于维护和更准确的关键",这是一种疯狂的过分夸大的单调性.我把它变成了"一把钥匙".


Jam*_*ney 12

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


dav*_*owl 7

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

Expression<Func<object, string>>
Run Code Online (Sandbox Code Playgroud)

我认为这是你真正想要的而不是委托(你使用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;
}
Run Code Online (Sandbox Code Playgroud)

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


小智 6

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

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

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

.Attributes(style => "width:100%");
Run Code Online (Sandbox Code Playgroud)

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

.Attributes.Style = "width:100%";
Run Code Online (Sandbox Code Playgroud)

甚至:

.Attributes.Style.Width.Percent = 100;
Run Code Online (Sandbox Code Playgroud)

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

  • 我很欣赏编译时检查,但我认为这归结为意见问题.也许像新的属性(){Style:"width:100%"}会赢得更多的人,因为它更简洁.尽管如此,实现HTML允许的所有内容都是一项艰巨的任务,我不能因为使用字符串/ lambdas /匿名类而责怪某人. (3认同)

Hor*_*ez. 5

实际上它看起来像Ruby =),至少对我来说,为后来的动态"查找"使用静态资源不适合api设计考虑因素,希望这个聪明的技巧在api中是可选的.

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

希望这可以帮助


mad*_*ode 5

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

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

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

  • 如果你没有从控制器继承,那么命名一个类控制器也不会蹲下... (4认同)