关于asp.net mvc:反映参数名称:滥用C#lambda表达式还是语法亮度?

Reflecting parameter name: abuse of C# lambda expressions or Syntax brilliance?

我正在看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表达式爱好者的常见做法吗?或者是一个无赖的独行侠,我不该担心?


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

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

这是一个在ASP.NET MVC中使用的模式(例如),还有其他用途(注意,如果名称是一个魔力值而不是特定于调用方的话,还要注意Ayend的想法)


这有较差的互操作性。例如,考虑这个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语言之间具有良好的互操作性,则应避免此类"滥用",或者至少提供"标准"重载(例如,将字符串名称作为额外参数)。


只是想表达我的观点(我是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)。


我宁愿

1
Attributes.Add(string name, string value);

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


欢迎来到Rails Land:)

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

Rails框架的整体是建立在对配置进行约定的思想之上的。以某种方式命名事物会使你进入他们正在使用的约定中,你可以免费获得很多功能。遵循命名约定可以让您更快地实现目标。整件事做得很好。

我看到的另一个技巧是moq中的方法调用断言。传入lambda,但从未执行lambda。他们只是使用表达式来确保方法调用发生,如果没有,则抛出异常。


这在多个层面上都是可怕的。不,这根本不像红宝石。这是对C和.NET的滥用。

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

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

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

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

  • 它完全反直觉,智能感知将提供很少的帮助,了解如何传递东西。

  • 它的效率不必要。

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

  • 属性的参数类型是什么,是Func?这个意图是如何揭示的?您的IntelliSense文档将要说什么,"请忽略对象的所有值"

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


我在"语法天才"阵营中,如果他们能清楚地记录下来,而且看起来很酷,我觉得这几乎没有问题!


他们俩。它充满了lambda表达式和语法的光辉。


我几乎从未遇到过这种用法。我认为这是"不适当的":)

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

欺骗

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

赞成的意见

  • 在开发人员调整到这种语法之后,它更易于阅读。

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


不,这当然不是常见的做法。这是违反直觉的,没有办法仅仅通过查看代码来了解它的功能。你必须知道它是如何被用来理解它是如何被使用的。

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

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

虽然这是一个更多的类型,它是明确和直观的。


以下内容有什么问题:

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


我能用这个造句吗?

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


所有这些关于"恐怖"的叫嚣都是一群长期的C家伙反应过度(我是一个长期的C程序员,还是一个非常喜欢这种语言的人)。这个语法没有什么可怕的。这只是一种让语法看起来更像你想要表达的东西的尝试。某些东西的语法中"噪音"越小,程序员就越容易理解它。减少一行代码中的噪声只会有一点点帮助,但是让它在越来越多的代码中累积起来,结果证明这是一个巨大的好处。

这是作者为DSL给你的同样好处而努力的一次尝试——当代码"看起来"像你想说的话时,你已经到达了一个神奇的地方。您可以争论这是否有利于互操作,或者它是否足够好于匿名方法来证明某些"复杂性"成本的合理性。很公平…因此,在您的项目中,您应该正确选择是否使用这种语法。但还是…这是一个程序员的聪明尝试,在一天结束的时候,我们都在尝试做什么(不管我们是否意识到)。我们都在努力做的是:"用语言告诉计算机我们想让它做什么,语言尽可能接近我们想让它做什么。"

越来越接近以我们认为内部的方式向计算机表达我们的指令,是使软件更易于维护和更准确的关键。

编辑:我说过"让软件更易于维护和更准确的关键",这是一个疯狂天真的夸大了独角兽。我把它改成了"钥匙"。


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


这是一个有趣的方法。如果只将表达式的右侧约束为常量,则可以使用

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强大的类型检查能力,这有助于防止错误首先进入代码。


嗯,这是一种很酷的方式。我们都喜欢这样一个事实:命名一个类控制器将使它成为MVC中的控制器,对吗?所以在有些情况下,命名确实很重要。

这里的意图也很清楚。很容易理解,.Attribute( book =>"something")会导致book="something".Attribute( log =>"something")会导致log="something"

我想如果你把它当作一种惯例来对待,那就不成问题了。我的观点是,无论什么都能让你写更少的代码,让你的意图变得明显,这是一件好事。


实际上,它看起来像ruby=),至少对我来说,使用静态资源进行稍后的动态"查找"不适合API设计考虑,希望这个巧妙的技巧在该API中是可选的。

我们可以从IDictionary继承(或不继承)并提供一个索引器,当您不需要添加键来设置值时,该索引器的行为类似于PHP数组。它将是对.NET语义的有效使用,而不仅仅是C,而且还需要文档。

希望这有帮助


在我看来,这是虐待羔羊。

至于语法的出色性,我发现style=>"width:100%"很容易混淆。特别是因为=>而不是=


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


我觉得这不比"魔法弦"好。我也不太喜欢匿名类型。它需要更好的强类型方法。