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

问题描述 投票:422回答:21

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

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

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

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表达爱好者的常见做法吗?或者是一个我不应该担心的流氓一招特立独行?

c# asp.net-mvc lambda mvccontrib
21个回答
146
投票

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


18
投票

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

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

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

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


17
投票

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

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


17
投票

以下是什么问题:

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

17
投票

所有这些关于“可怕”的咆哮都是一群反复过度的长期c#家伙(我是一名长期的C#程序员,仍然是该语言的忠实粉丝)。这种语法没什么可怕的。它只是试图使语法看起来更像你想要表达的内容。语法中的“噪音”越少,程序员就越容易理解它。减少一行代码中的噪声只会有所帮助,但是让它在越来越多的代码中积累,结果证明是一个重要的好处。

这是作者试图争取DSL给你的同样的好处 - 当代码只是“看起来像”你想说的时候,你已经到了一个神奇的地方。您可以讨论这是否适用于互操作,或者它是否比匿名方法更好,以证明某些“复杂性”成本。足够公平......所以在你的项目中你应该正确选择是否使用这种语法。但仍然......这是一个程序员做一些聪明的尝试,在一天结束时,我们都在尝试做(无论我们是否意识到)。而我们所有人都想做的是:“告诉计算机我们希望用尽可能接近我们想要做什么的语言做什么。”

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

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


12
投票

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


7
投票

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

Expression<Func<object, string>>

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

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


6
投票

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

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

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

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

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

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

甚至:

.Attributes.Style.Width.Percent = 100;

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


5
投票

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

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

希望这可以帮助


5
投票

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

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

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


4
投票

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

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


154
投票

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

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

这是在ASP.NET MVC的大部分中使用的模式(例如),并且有other usescaveat,如果名称是魔术值而不是调用者特定,请注意Ayende's thoughts


3
投票

如果方法(func)名称被很好地选择,那么这是避免维护麻烦的一种很好的方法(即:添加新的func,但忘记将其添加到函数参数映射列表中)。当然,您需要对其进行大量记录,并且最好从该类中的函数的文档中自动生成参数的文档...


1
投票

我认为这并不比“魔术弦”好。对于这个,我不太喜欢匿名类型。它需要一种更好的强类型方法。


137
投票

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

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

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

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

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

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

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


48
投票

我会比较喜欢

Attributes.Add(string name, string value);

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


46
投票

欢迎来到Rails Land :)

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

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

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


42
投票

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

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

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

  • 当你需要从VB调用它时会发生什么? .Attributes(Function(style) "width:100%")
  • 它完全反直觉,智能感知将无法帮助确定如何传递内容。
  • 它的效率不必要地低效。
  • 没有人会知道如何维护它。
  • 参与属性的论证的类型是什么,是Func<object,string>?这个意图是如何揭示的。您的intellisense文档会说什么,“请忽略对象的所有值”

我觉得你有充分的理由感到有这种反感。


40
投票

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


37
投票

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


21
投票

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

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

缺点

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

优点

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

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

© www.soinside.com 2019 - 2024. All rights reserved.