反映参数名:滥用c# lambda表达式或语法辉煌?

我正在查看 Grid组件,我被网格的语法中使用的语法技巧所吸引,但同时又被排斥:

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

上面的语法将生成的HTML的样式属性设置为width:100%。如果你注意的话,'style'并没有被指定。它是从表达式中参数的的名字推导出来的!我必须深入研究这个问题,找到“魔法”发生的地方:

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表达式爱好者的普遍做法吗?还是说,我不应该担心一个流氓的诡计特立独行?

29720 次浏览

我更喜欢

Attributes.Add(string name, string value);

它更加明确和标准,使用lambdas什么也得不到。

这是表达式树的好处之一——可以检查代码本身以获得额外的信息。这就是如何将.Where(e => e.Name == "Jamie")转换为等效的SQL Where子句。这是表达式树的一种巧妙使用,尽管我希望它不会超出这个范围。任何更复杂的代码都可能比它希望取代的代码更难,因此我怀疑它将是自我限制的。

我属于“语法卓越”阵营,如果他们清楚地记录它,它看起来非常酷,在我看来几乎没有问题!

欢迎来到Rails Land:)

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

整个Rails框架是建立在约定优于配置的思想之上的。以特定的方式命名事物将您带入他们正在使用的约定,并且您可以免费获得大量功能。遵循命名惯例可以让你更快地到达目的地。整个系统运作得非常出色。

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

它的互操作性很差。例如,考虑这个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语言之间有良好的互操作。

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

这不是一种常用的使用方式,它不符合一般的约定。当然,这种语法也有优点和缺点:

缺点

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

优点

  • 在开发人员调整到这种语法之后,它的可读性更强。

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

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

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

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

虽然输入起来有点费劲,但它清晰直观。

如果方法(func)名称选择得当,那么这是避免维护头痛的好方法(例如:添加一个新的func,但忘记将其添加到函数参数映射列表中)。当然,你需要对它进行大量的文档记录,你最好从该类函数的文档中自动生成参数的文档……

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

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

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

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

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

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

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

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

甚至:

.Attributes.Style.Width.Percent = 100;

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

我发现这很奇怪,不是因为的名字,而是因为没有必要;它可以使用匿名类型,并且更加灵活:

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

这是在很多ASP中使用的模式。NET MVC(例如),并具有其他的用途(一个警告,如果名称是一个魔法值而不是特定于调用者的值,也请注意Ayende的想法)

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

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

希望这能有所帮助

这是在不止一个层次上的可怕的。不,这一点也不像露比。这是对c#和。net的滥用。

关于如何以更直接的方式实现这一点,已经有许多建议:元组、匿名类型、流畅接口等等。

它之所以如此糟糕,是因为它只是为了自己的利益而幻想:

  • 当你需要从Visual Basic调用这个时会发生什么?

    .Attributes(Function(style) "width:100%") < / p >

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

  • 这是不必要的低效。

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

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

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

两个都是。它滥用lambda表达式语法的光辉。

只是想提出我的观点(我是MvcContrib网格组件的作者)。

毫无疑问,这绝对是语言滥用。然而,我并不真的认为这是违反直觉的-当你查看Attributes(style => "width:100%", @class => "foo")
的调用 我认为这是非常明显的(它当然不比匿名类型方法更糟糕)。从智能感知的角度来看,我同意这是相当不透明的

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

我把它添加到网格作为个人偏好-我不喜欢使用匿名类型作为字典(有一个接受“object”的参数就像一个接受参数Func[]一样不透明)和字典集合初始化器相当冗长(我也不喜欢冗长的流畅接口,例如必须将多个调用链接到一个属性(“style”,“display:none”)。属性("class", "foo")等)

如果c#对字典字面量有一个不那么冗长的语法,那么我就不会在网格组件中包含这个语法了:)

我还想指出,在MvcContrib中使用这个是完全可选的-这些是扩展方法,包装重载,以字典代替。我认为重要的是,如果你提供了这样的方法,你也应该支持更“正常”的方法,例如与其他语言的互操作。

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

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

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

我想如果你把它当作一种惯例来对待,那应该不成问题。我的观点是,只要能让你写更少的代码,让你的意图更明显,那就是一件好事。

这是一个有趣的方法。如果你约束右边的表达式为常数,那么你可以实现使用

Expression<Func<object, string>>

我认为这是你真正想要的而不是委托(你使用lambda来获得双方的名称) 参见下面的naive实现:

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

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

我能用这个词造个词吗?

Magic lambda (n):仅用于替换魔术字符串的lambda函数。

我认为这不比“魔术弦”好多少。在这方面,我也不太喜欢匿名类型。它需要一个更好的&强类型方法。

下面的问题是什么:

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

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

作者试图获得与DSL相同的好处——当代码“看起来”像您想要表达的内容时,您就达到了一个神奇的境界。您可以讨论这是否有利于互操作,或者它是否比匿名方法更好,以证明一些“复杂性”成本是合理的。有道理……所以在你的项目中,你应该正确选择是否使用这种语法。但是……这是一个程序员的一个聪明的尝试,在一天结束的时候,我们都在尝试做什么(不管我们是否意识到)。我们都在努力做的是:“用尽可能接近我们想让它做什么的语言告诉计算机我们想让它做什么。”

使软件更易于维护、更准确的关键在于,我们能够更接近于用我们内心认为的方式向计算机表达指令。

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