我正在看mvcontrib网格组件,我对网格语法中使用的语法技巧着迷,但同时又感到厌恶:
1
| .Attributes(style =>"width:100%") |
上面的语法将生成的HTML的样式属性设置为width:100%。现在,如果您注意到,没有指定"样式",而是从表达式中参数的名称中推导出来的!我必须深入研究这个问题,找到"魔法"发生的地方:
1 2 3 4 5 6 7
| 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))来写,这意味着同样的事情。但是,使用这里的mvcontrib网格语法,我突然发现了一些代码,这些代码可以根据我为变量选择的名称进行积极的查找和决策!
这是C 3.5/4.0社区和lambda表达式爱好者的常见做法吗?或者是一个无赖的独行侠,我不该担心?
- +1把火箭带到C#
- 将foreach(hash中的var func)更改为:array.forearch(hash,func=>add(func.method.getParameters()[0].name,func(null)));
- 我认为这看起来很明显,只要您愿意了解代码的意图,而不仅仅是语法分析。如果你读的是好的代码,无论如何你都应该这样做。语法只是意图的工具,我认为这是意图揭示代码。
- 他们以前是Perl用户吗?在这里,style =>"width:100%"在各个方面都等同于"style","width:100%"。
- @埃里克,当编译器团队的一部分做出这样的评论时,情况一定很糟糕。也许问问安德斯他是怎么想的?
- 我正在等待stackoverflow.com/users/8560介入并保护他的代码…
- 这只是在自找麻烦和意想不到的后果。
- 我只是问安德斯(和其他设计团队)他们的想法。让我们假设结果不会在家庭友好型报纸上打印出来。
- @埃里克·利珀特:有波蒂·索西的淫秽的蓝底颜色的卑鄙的非家庭友好型报纸吗?我在哪里能找到这些?另外,你能详细描述一下恐怖的细节吗?我真的很想听听你们为什么认为这很糟糕…
- 当然。西雅图是"陌生人"。至于为什么这是可怕的,我们可以从不显眼的,聪明的(记住,聪明是坏的,聪明的代码是难以维护的),根本不在兰伯斯的设计者设想的设计用例内,缓慢,脆弱,不可移植,和不必要的。
- C目前缺少一个干净、简单的映射语法,尤其是传递给函数的映射。各种动态语言(ruby、python、javascript、coldfusion 9)都有一个清晰的、简单的语法,从某种程度上来说是如此。
- @埃里克谢谢你的邀请。我在下面已经论证过,这是一个好看的语法,但可以理解你反对它的论点。这些都是不做某件事的正当理由。
- 哦,我觉得它看起来很可爱。至于地图的语法,是的,如果我们能做新的"做"、"鹿"、"re"、"金色太阳"……}让编译器推断映射的构造,就像新的[]1、2、3推断int数组的构造一样。我们正在考虑这些事情。
- 什么是语法映射btw?
- "map"是.NET库所称的"dictionary"——一种将键(键可以是一个键,一对键,随便什么)映射到值上的设备。编程中的许多概念实际上只是一种绘制地图的奇特方法。例如,您可以将someobject.someproperty视为从对(someobject的值,someproperty的名称)到属性值的映射。因为这个想法是如此的基础,所以当编程语言提供了一个用于描述任意映射的光滑语法时,这是很好的。集合初始值设定项是一个很好的开始。
- 刚刚由K.Scott Allen写的博客——"你的可憎是我的聪明黑客"odetocode.com/blogs/scott/archive/2009/11/30/&hellip;
- 所以我想这就是当有人想使用铁轨但知道C时会发生的事情。并不是你在ror中所能做的一切都需要移植到其他语言和框架中。
- 我试着回答我博客上提出的一些问题:jeremyskinner.co.uk/2009/12/02/lambda-abuse-the-mvcontrib-h&zwnj;&8203;ash
- lambda将成为.NET Shrug的正则表达式
- 埃里克·利珀特,我非常尊敬你,但我认为你和这个团体(包括我非常尊敬的安德斯)对这件事过于严厉了。正如您所承认的,C对于映射缺乏严格的语法,而其他一些语言(如Ruby)则具有很好的语法。这家伙找到了一种方法来获得他想要的语法。我同意你有类似的方法来表达它,几乎和他的语法一样有表达力,而且缺点更少。但他的句法和他如此努力地获得它的事实清楚地表明了语言增强的必要性。过去的表现表明你们会为它创造一些伟大的东西。
- 我想知道如果优化得太多,或者更糟,会发生什么-模糊化。
- 我不会雇用任何一个这样做或不立即理解为什么不应该这样做的人。
- 我喜欢C,但它并不是一种语言最能表达的。有很多元编程概念是C_无法表达的,但是我们遇到了一些问题,这些问题的解决方案需要或可能从中受益匪浅。CLR隐藏了各种低级的魔法滥用,一些人也抱怨它。但是看看它能让我们做什么!和这个lambda技巧一样。这是一个全面的解决方案,以其他丑陋的C表达,它似乎很好地记录,它的使用/目的是显而易见的视觉,它帮助我们创建酷网站迅速。
- 匿名类型为idictionary将是更好的选择imho。而且它在ASP.NET MVC中的很多地方都有使用。具有简单init语法的map类型将非常好(与匿名类型相似吗?)编辑:没看到马克·格雷威尔的回答…
- 谁是这里的贡献者和贡献者?:)
- 仅凭这样一个事实,即有效的HTML属性名不一定是有效的C标识符名,考虑保留关键字,这是不合格的。
我发现这很奇怪,不是因为名字,而是因为lambda是不必要的;它可以使用匿名类型,并且更加灵活:
1
| .Attributes(new { style ="width:100%", @ class="foo", blip =123 }); |
这是一个在ASP.NET MVC中使用的模式(例如),还有其他用途(注意,如果名称是一个魔力值而不是特定于调用方的话,还要注意Ayend的想法)
- 这也存在互操作问题;并非所有语言都支持创建这样的匿名类型。
- 我喜欢没有人真正回答这个问题,相反,人们提出了一个"这是更好的"论点。:p这是不是滥用?
- 这都是关于可读性的。这种方法涉及更多的大括号和new关键字。你能补充一下为什么这个(使用匿名类型)是more flexible吗?
- 我想看看埃里克·利珀特对此的反应。因为它在框架代码中。这也很可怕。
- 我认为问题不在于代码编写后的可读性。我认为真正的问题是代码的可学习性。当你的intellisense说.attributes(object obj)时,你会怎么想?您必须阅读文档(没有人愿意这样做),因为您不知道要传递给方法什么。我不认为这真的比这个问题中的例子更好。
- @arnis——为什么更灵活:它不依赖于隐含的参数名,这可能(不引用我)导致一些lambda实现(其他语言)出现问题——但是您也可以使用具有定义属性的常规对象。例如,您可以有一个具有预期属性(用于intellisense)的HtmlAttributes类,而忽略那些具有null值的类……
- (有关lambda实现wosit的说明,请参阅上面的f示例-我知道我在某个地方见过它!)
- 如果能够将匿名对象直接发送到函数(IDictionary X),这会很好,有点像函数中的an expression>参数适用于lambda。
这有较差的互操作性。例如,考虑这个c-f示例
C:
1 2 3 4 5 6 7
| public class Class1
{
public static void Foo(Func<object, string> f)
{
Console.WriteLine(f.Method.GetParameters()[0].Name);
}
} |
F:
1
| Class1.Foo(fun yadda ->"hello") |
结果:
打印"arg"(不是"yadda")。
因此,如果库设计人员希望在.NET语言之间具有良好的互操作性,则应避免此类"滥用",或者至少提供"标准"重载(例如,将字符串名称作为额外参数)。
- 一些我没有考虑过的事情。如何正确访问表达式树,使其在f情况下工作?
- 你没有。这个策略是不可移植的。(另一方面,如果它有帮助,作为一个例子,F可以重载只在返回类型上不同的方法(类型推断可以做到这一点)。这可以用clr表示。但是F不允许这样做,主要是因为如果你这样做了,那些API就不能从C调用了。当涉及到互操作时,总是在"边缘"特性上权衡你得到了什么好处,而不是你交换了什么互操作。
- 这似乎有点疏漏了F语言方面的内容——这是否是一个很好的理由?我猜测C中的lambda表达式和F中的函数会生成不同的IL。
- 我不喜欢非互操作性作为不做某事的理由。如果互操作性是一项需求,那么就做它,如果不是,那么为什么要担心它呢?我是亚格尼·伊莫。
- @jfar:in.net clr land互操作性有一个全新的维度,因为在任何编译器中生成的程序集都应该从任何其他语言中使用。
- @鲁萨诺:我认为只要你不声称自己是(CLSCompliant),做不可互操作的事情是可以的。
- @杰弗里:你说得对。
- 我同意你不必遵守CLS,但是如果你正在编写一个库或控件(从网格开始的代码片段,是吗?)否则,你只是在限制你的受众/客户群
- +1表示互操作问题。它只是众多中的一个。
- 也许值得将:func
- @DFowler:那么,func.Parameters[0].Name比func.Method.GetParameters()[0].Name有更好的互操作支持吗?你是这么说的吗?
- 我喜欢lambda语法,但就在今天我发现了它的另一个问题。它不能与新的HTML5"data-"属性一起工作,因为类似于data key的东西不是有效的C标识符。好消息是有太多的东西需要依靠。
- @Kyralessa您还可以自己创建一个扩展方法,专门针对数据属性。比如说,.Data(myField => whatever)。现在,您有了比使用Attribute更清楚的东西——毕竟,您在语义上试图传递数据,而不是属性。
只是想表达我的观点(我是mvcontrib网格组件的作者)。
这绝对是语言滥用——毫无疑问。然而,我并不认为这是违反直觉的——当你看到一个对Attributes(style =>"width:100%", @class =>"foo")的呼叫时。我认为这很明显发生了什么(这当然不比匿名类型的方法更糟糕)。从智能感知的角度来看,我同意这是相当不透明的。
对于那些感兴趣的人,关于它在mvcontrib中的使用的一些背景信息…
我把它作为个人偏好添加到网格中-我不喜欢使用匿名类型作为字典(有一个参数取"object"和一个参数取params func[]一样不透明),字典集合初始值设定项相当冗长(我也不喜欢冗长流畅的接口,例如必须将多个C链接在一起)。调用到属性("style"、"display:none").attribute("class"、"foo")等)
如果C对字典文本有一个不那么冗长的语法,那么我就不会费心在网格组件中包含这种语法了:)
我还想指出,在mvcontrib中使用它是完全可选的——这些是包装采用IDictionary的重载的扩展方法。我认为重要的是,如果您提供这样的方法,那么您还应该支持更"普通"的方法,例如用于与其他语言的互操作。
另外,有人提到了"反射开销",我只是想指出,这种方法实际上没有太多的开销——不涉及运行时反射或表达式编译(请参见http://blog.bittercoder.com/permalink,guid,206e64d1-29ae-4362-874b-83f5b103727f.aspx)。
- 我还尝试在我的博客上更深入地讨论这里提出的一些问题:jeremyskinner.co.uk/2009/12/02/lambda-abuse-the-mvcontrib-h&zwnj;&8203;ash
- 它在intellisense中的不透明性不亚于匿名对象。
- +1表示通过可选的扩展方法添加接口。非C用户(以及任何被语言滥用冒犯的人)可以简单地避免使用它。
我宁愿
1
| Attributes.Add(string name, string value); |
它更为明确和标准,使用lambda什么也得不到。
- 是吗?html.Attributes.Add("style","width:100%");的读取效果不如style ="width:100%"(实际生成的HTML)好,而style =>"width:100%"与生成的HTML非常接近。
- 它们的语法允许使用.attributes(id=>'foo',@class=>'bar',style=>'width:100%')等技巧。函数签名使用params语法来表示变量数args:attributes(params func
- 同意。更具可读性。
- @杰米:试图使C代码看起来像HTML代码是设计决策的一个坏理由。它们是完全不同的语言,用于完全不同的目的,它们看起来不应该相同。
- 一个匿名的对象也可能被使用,而不牺牲"美"?.attributes(new id="foo",@class="bar",style="width:100%"))??
- @Guffa为什么它会成为设计决策的一个坏理由?为什么它们看起来不一样?根据这种推理,他们应该故意看起来不一样吗?我不是说你错了,我只是说你可能想更详细地阐述你的观点。
- @斯图尔特:让输出HTML的代码看起来像HTML,就像让打印条码的代码看起来像条码一样……代码的编写方式不应受到输出外观的影响。故意让代码看起来不同并不真正适用于这里,但在ASP代码中,我故意夸大了vbscript和javascript之间的差异,以便于识别。
- 我完全同意,但这根本不能回答这个问题。
- @Guffa,通过这种推理,你永远不会使用像ASP.NET、ASP.NET MVC、JSP或任何类似的Web技术。它们存在的原因是让视图代码尽可能接近HTML。在任何一个servlet出现之前,我们就已经有了servlet,servlet只是一种从代码生成文本HTML的方法。世界想要的东西看起来更像是正在生成的输出,显然自从你提到ASP之后你也这样做了。
欢迎来到Rails Land:)
只要你知道发生了什么事,它就没有什么问题。(当这种事情没有很好的记录时,就出现了问题)。
Rails框架的整体是建立在对配置进行约定的思想之上的。以某种方式命名事物会使你进入他们正在使用的约定中,你可以免费获得很多功能。遵循命名约定可以让您更快地实现目标。整件事做得很好。
我看到的另一个技巧是moq中的方法调用断言。传入lambda,但从未执行lambda。他们只是使用表达式来确保方法调用发生,如果没有,则抛出异常。
- 我有点犹豫,但我同意。除了反射开销之外,在add()中使用字符串与使用lambda参数名之间没有显著差异。至少我能想到。你可以把它弄脏,输入"Sytle",而不需要注意这两种方式。
- 我不明白为什么这对我来说并不奇怪,然后我想起了Rails。D
这在多个层面上都是可怕的。不,这根本不像红宝石。这是对C和.NET的滥用。
对于如何以更直接的方式实现这一点,有很多建议:元组、匿名类型、流畅的接口等等。
它之所以如此糟糕,是因为它只是为了自己的利益而幻想:
当你需要从vb中调用它时会发生什么?
.Attributes(Function(style)"width:100%")
它完全反直觉,智能感知将提供很少的帮助,了解如何传递东西。
它的效率不必要。
没有人会知道如何维护它。
属性的参数类型是什么,是Func?这个意图是如何揭示的?您的IntelliSense文档将要说什么,"请忽略对象的所有值"
我认为你完全有理由有这种反感的感觉。
- 我会说-这完全是直觉。:)
- 你说它和红宝石没什么两样。但这与Ruby指定哈希表的键和值的语法非常相似。
- 在alpha转换下中断的代码!哎呀!
- @查理,句法上它看起来很相似,语义上它有很大的不同。
我在"语法天才"阵营中,如果他们能清楚地记录下来,而且看起来很酷,我觉得这几乎没有问题!
- 阿门,兄弟。阿门(第二个阿门需要满足最小长度的意见:)
- 你的评论远远超出了需要。但是,你不能只是一次阿门然后发表你的评论:d
他们俩。它充满了lambda表达式和语法的光辉。
- 所以这是对lambda表达式的一种出色的语法滥用?我想我同意:)
我几乎从未遇到过这种用法。我认为这是"不适当的":)
这不是一种常见的使用方法,它与一般惯例不一致。当然,这种语法有利弊:
欺骗
- 代码不直观(通常的惯例不同)
- 它往往是脆弱的(参数的重命名将破坏功能)。
- 测试起来有点困难(伪造API需要在测试中使用反射)。
- 如果表达式被大量使用,它会因为需要分析参数而不仅仅是值(反射成本)而变慢。
赞成的意见
底线——在公共API设计中,我会选择更明确的方式。
- @以利沙——你的利弊颠倒了。至少我希望你不是说专业人士的代码"不直观"。;-)
- 对于这种特殊情况,lambda参数名称和字符串参数都是脆弱的。这就像使用动态XML解析——这是适当的,因为您无论如何都不能确定XML。
不,这当然不是常见的做法。这是违反直觉的,没有办法仅仅通过查看代码来了解它的功能。你必须知道它是如何被用来理解它是如何被使用的。
与使用委托数组提供属性不同,链接方法更清晰,性能更好:
1
| .Attribute("style","width:100%;").Attribute("class","test") |
虽然这是一个更多的类型,它是明确和直观的。
- 真的?当我看到这段代码时,我很清楚它的用意。除非你很严格,否则就没那么迟钝了。我们可以给出关于字符串连接的overloading+的相同参数,我们应该始终使用concat()方法。
- @斯图尔特:不,你不太清楚,你只是根据所用的值来猜测。任何人都可以猜测,但猜测并不是理解代码的好方法。
- 我猜用.Attribute("style","width:100%")会给我style="width:100%",但据我所知,它会给我foooooo。我看不出有什么不同。
- "根据使用的值进行猜测"总是您在查看代码时所做的。如果您遇到对stream.close()的调用,您假设它关闭了一个流,但它也可能做一些完全不同的事情。
- @沃特:如果你在阅读代码时总是猜测,那么你一定很难阅读代码……如果我看到对一个名为"close"的方法的调用,我会认为这个类的作者不知道命名约定,所以对于这个方法所做的一切,我都会犹豫不决。
以下内容有什么问题:
1
| html.Attributes["style"] ="width:100%"; |
我能用这个造句吗?
magic lambda(n):仅用于替换魔术字符串的lambda函数。
- 是啊。。。真有趣。也许这是不可思议的,在没有编译时安全性的意义上,有没有一个地方会导致运行时而不是编译时错误?
所有这些关于"恐怖"的叫嚣都是一群长期的C家伙反应过度(我是一个长期的C程序员,还是一个非常喜欢这种语言的人)。这个语法没有什么可怕的。这只是一种让语法看起来更像你想要表达的东西的尝试。某些东西的语法中"噪音"越小,程序员就越容易理解它。减少一行代码中的噪声只会有一点点帮助,但是让它在越来越多的代码中累积起来,结果证明这是一个巨大的好处。
这是作者为DSL给你的同样好处而努力的一次尝试——当代码"看起来"像你想说的话时,你已经到达了一个神奇的地方。您可以争论这是否有利于互操作,或者它是否足够好于匿名方法来证明某些"复杂性"成本的合理性。很公平…因此,在您的项目中,您应该正确选择是否使用这种语法。但还是…这是一个程序员的聪明尝试,在一天结束的时候,我们都在尝试做什么(不管我们是否意识到)。我们都在努力做的是:"用语言告诉计算机我们想让它做什么,语言尽可能接近我们想让它做什么。"
越来越接近以我们认为内部的方式向计算机表达我们的指令,是使软件更易于维护和更准确的关键。
编辑:我说过"让软件更易于维护和更准确的关键",这是一个疯狂天真的夸大了独角兽。我把它改成了"钥匙"。
这是表达式树的好处之一——可以检查代码本身以获取额外信息。这就是如何将.Where(e => e.Name =="Jamie")转换为等效的SQL WHERE子句。这是表达树的巧妙运用,尽管我希望它不会比这更进一步。任何更复杂的代码都可能比它希望替换的代码更困难,所以我怀疑它会自我限制。
- 这是一个有效的观点,但广告中的真相是:Linq有一整套属性,如TableAttribute和ColumnAttribute,这使得它更合法。此外,Linq映射还查看类名和属性名,它们可以说比参数名更稳定。
- 我同意你的看法。在阅读了埃里克·利珀特(EricLippert)/安德斯·赫尔斯伯格(AndersHelsberg)/等对此事的评论后,我对这一点的立场略有改变。我想我会留下这个答案,因为它仍然有一些帮助。值得一提的是,我现在认为这种使用HTML的方式很好,但不适合这种语言。
这是一个有趣的方法。如果只将表达式的右侧约束为常量,则可以使用
1
| Expression<Func<object, string>> |
我认为这是你真正想要的,而不是代表(你用lambda来获取双方的名字)参见下面的幼稚实现:
1 2 3 4 5 6 7
| 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 ;
} |
这甚至可以解决线程前面提到的跨语言互操作问题。
代码非常聪明,但它可能会导致更多的问题。
正如您所指出的,现在参数名(样式)和HTML属性之间有一个模糊的依赖关系。未完成编译时检查。如果参数名称输入错误,页面可能不会有运行时错误消息,但很难找到逻辑错误(没有错误,但行为不正确)。
更好的解决方案是拥有一个可以在编译时检查的数据成员。因此,不要这样:
1
| .Attributes(style =>"width:100%"); |
编译器可以检查具有Style属性的代码:
1
| .Attributes.Style ="width:100%"; |
甚至:
1
| .Attributes.Style.Width.Percent = 100; |
对于代码的作者来说,这是更多的工作,但是这种方法利用了C强大的类型检查能力,这有助于防止错误首先进入代码。
- 我很欣赏编译时检查,但我认为这归根结底是一个意见问题。也许像new attributes()style:"width:100%"这样的东西会赢得更多的人,因为它更简洁。尽管如此,实现HTML所允许的一切都是一项艰巨的工作,我不能责怪某人只使用字符串/lambdas/anonymous类。
嗯,这是一种很酷的方式。我们都喜欢这样一个事实:命名一个类控制器将使它成为MVC中的控制器,对吗?所以在有些情况下,命名确实很重要。
这里的意图也很清楚。很容易理解,.Attribute( book =>"something")会导致book="something",.Attribute( log =>"something")会导致log="something"。
我想如果你把它当作一种惯例来对待,那就不成问题了。我的观点是,无论什么都能让你写更少的代码,让你的意图变得明显,这是一件好事。
- 命名一个类控制器不会做蹲坐,但是如果你不从控制器继承太多…
实际上,它看起来像ruby=),至少对我来说,使用静态资源进行稍后的动态"查找"不适合API设计考虑,希望这个巧妙的技巧在该API中是可选的。
我们可以从IDictionary继承(或不继承)并提供一个索引器,当您不需要添加键来设置值时,该索引器的行为类似于PHP数组。它将是对.NET语义的有效使用,而不仅仅是C,而且还需要文档。
希望这有帮助
在我看来,这是虐待羔羊。
至于语法的出色性,我发现style=>"width:100%"很容易混淆。特别是因为=>而不是=
如果方法(func)名称选择得很好,那么这是一种避免维护难题的绝妙方法(即:添加一个新的func,但忘记将其添加到函数参数映射列表中)。当然,您需要对它进行大量的文档记录,最好是自动生成文档,以获取该类中函数的文档中的参数…
我觉得这不比"魔法弦"好。我也不太喜欢匿名类型。它需要更好的强类型方法。