This is a pattern used in much of ASP.NET MVC (for example), and has other uses (a caveat, note also Ayende's thoughts if the name is a magic value rather than caller-specific)
This has poor interop. For example, consider this C# - F# example
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")
Result:
"arg" is printed (not "yadda").
As a result, library designers should either avoid these kinds of 'abuses', or else at least provide a 'standard' overload (e.g. that takes the string name as an extra parameter) if they want to have good interop across .Net languages.
Just wanted to throw in my opinion (I'm the author of the MvcContrib grid component).
This is definitely language abuse - no doubt about it. However, I wouldn't really consider it counter intuitive - when you look at a call to Attributes(style => "width:100%", @class => "foo") I think it's pretty obvious what's going on (It's certainly no worse than the anonymous type approach). From an intellisense perspective, I agree it is pretty opaque.
For those interested, some background info on its use in MvcContrib...
I added this to the grid as a personal preference - I do not like the use of anonymous types as dictionaries (having a parameter that takes "object" is just as opaque as one that takes params Func[]) and the Dictionary collection initializer is rather verbose (I am also not a fan of verbose fluent interfaces, eg having to chain together multiple calls to an Attribute("style", "display:none").Attribute("class", "foo") etc)
If C# had a less verbose syntax for dictionary literals, then I wouldn't have bothered including this syntax in the grid component :)
I also want to point out that the use of this in MvcContrib is completely optional - these are extension methods that wrap overloads that take an IDictionary instead. I think it's important that if you provide a method like this you should also support a more 'normal' approach, eg for interop with other languages.
There is really nothing wrong with it as long as you know what's going on. (It's when this kind of thing isn't documented well that there is a problem).
The entirety of the Rails framework is built on the idea of convention over configuration. Naming things a certain way keys you into a convention they're using and you get a whole lot of functionality for free. Following the naming convention gets you where you're going faster. The whole thing works brilliantly.
Another place where I've seen a trick like this is in method call assertions in Moq. You pass in a lambda, but the lambda is never executed. They just use the expression to make sure that the method call happened and throw an exception if not.
This is horrible on more than one level. And no, this is nothing like Ruby. It's an abuse of C# and .NET.
There have been many suggestions of how to do this in a more straightforward way: tuples, anonymous types, a fluent interface and so on.
What makes it so bad is that its just way to fancy for its own good:
What happens when you need to call this from Visual Basic?
.Attributes(Function(style) "width:100%")
It's completely counter intuitive, and intellisense will provide little help figuring out how to pass stuff in.
It's unnecessarily inefficient.
Nobody will have any clue how to maintain it.
What is the type of the argument going in to attributes? is it Func<object,string>? How is that intention revealing? What is your intellisense documentation going to say, "Please disregard all values of object"?
I think you are completely justified having those feelings of revulsion.
No, it's certainly not common practice. It's counter-intuitive, there is no way of just looking at the code to figure out what it does. You have to know how it's used to understand how it's used.
Instead of supplying attributes using an array of delegates, chaining methods would be clearer and perform better:
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."
Getting closer to expressing our instructions to computers in the same manner that we think internally is a key to making software more maintainable and more accurate.
EDIT: I had said "the key to making software more maintainable and more accurate", which is a crazily naive overstated bit of unicorniness. I changed it to "a key."
This is one of the benefits of expression trees - one can examine the code itself for extra information. That is how .Where(e => e.Name == "Jamie") can be converted into the equivalent SQL Where clause. This is a clever use of expression trees, though I would hope that it does not go any further than this. Anything more complex is likely to be more difficult than the code it hopes to replace, so I suspect it will be self limiting.
The code is very clever, but it potentially causes more problems that it solves.
As you've pointed out, there's now an obscure dependency between the parameter name (style) and an HTML attribute. No compile time checking is done. If the parameter name is mistyped, the page probably won't have a runtime error message, but a much harder to find logic bug (no error, but incorrect behavior).
A better solution would be to have a data member that can be checked at compile time. So instead of this:
.Attributes(style => "width:100%");
code with a Style property could be checked by the compiler:
.Attributes.Style = "width:100%";
or even:
.Attributes.Style.Width.Percent = 100;
That's more work for the authors of the code, but this approach takes advantage of C#'s strong type checking ability, which helps prevent bugs from getting into code in the first place.
indeed its seems like Ruby =), at least for me the use of a static resource for a later dynamic "lookup" doesn't fit for api design considerations, hope this clever trick is optional in that api.
We could inherit from IDictionary (or not) and provide an indexer that behaves like a php array when you dont need to add a key to set a value. It will be a valid use of .net semantics not just c#, and still need documentation.
IMHO, it is a cool way of doing it. We all love the fact that naming a class Controller will make it a controller in MVC right? So there are cases where the naming does matter.
Also the intention is very clear here. It is very easy to understand that .Attribute( book => "something") will result in book="something" and .Attribute( log => "something") will result in log="something"
I guess it should not be a problem if you treat it like a convention. I am of the opinion that whatever makes you write less code and makes the intention obvious is a good thing.
If the method (func) names are well chosen, then this is a brilliant way to avoid maintenance headaches (ie: add a new func, but forgot to add it to the function-parameter mapping list). Of course, you need to document it heavily and you'd better be auto-generating the documentation for the parameters from the documentation for the functions in that class...
I think this is no better than "magic strings". I'm not much of a fan of the anonymous types either for this. It needs a better & strongly typed approach.
发布评论
评论(21)
我发现这很奇怪,并不是因为名称,而是因为lambda 是不必要的;它可以使用匿名类型并且更加灵活:
这是在 ASP.NET MVC 的大部分中使用的模式(例如),并且具有 其他用途(警告,另请注意Ayende 的想法(如果名称是一个神奇值而不是特定于调用者)
I find that odd not so much because of the name, but because the lambda is unnecessary; it could use an anonymous-type and be more flexible:
This is a pattern used in much of ASP.NET MVC (for example), and has other uses (a caveat, note also Ayende's thoughts if the name is a magic value rather than caller-specific)
这互操作性很差。例如,考虑此 C# - F# 示例
C#:
F#:
结果:
打印“arg”(而不是“yadda”)。
因此,如果库设计者想要在 .Net 语言之间实现良好的互操作,那么他们应该要么避免这些类型的“滥用”,要么至少提供一个“标准”重载(例如,将字符串名称作为额外参数)。
This has poor interop. For example, consider this C# - F# example
C#:
F#:
Result:
"arg" is printed (not "yadda").
As a result, library designers should either avoid these kinds of 'abuses', or else at least provide a 'standard' overload (e.g. that takes the string name as an extra parameter) if they want to have good interop across .Net languages.
只是想表达我的意见(我是 MvcContrib 网格组件的作者)。
这绝对是语言滥用——毫无疑问。但是,当您查看对
Attributes(style => "width:100%", @class => "foo") 的调用
我认为发生的事情非常明显(它肯定不比匿名类型方法差)。从智能感知的角度来看,我同意它是相当不透明的。
对于那些感兴趣的人,一些关于它在 MvcContrib 中使用的背景信息...
我将其作为个人偏好添加到网格中 - 我不喜欢使用匿名类型作为字典(具有采用“对象”的参数就像不透明一样)作为采用 params Func[]) 和 Dictionary 集合初始值设定项的一个相当冗长的(我也不喜欢冗长的流畅接口,例如必须将多个调用链接在一起对 Attribute("style", "display:none") .Attribute("class", "foo") 等)
如果 C# 对于字典文字有一个不太详细的语法,那么我就不会费心在网格组件中包含这个语法:)
我还想指出,使用MvcContrib 中的 this 是完全可选的 - 这些扩展方法包装了采用 IDictionary 的重载。我认为重要的是,如果您提供这样的方法,您还应该支持更“正常”的方法,例如与其他语言的互操作。
另外,有人提到了“反射开销”,我只是想指出,这种方法实际上没有太多开销 - 不涉及运行时反射或表达式编译(请参阅 http://blog.bittercoder.com/PermaLink,guid,206e64d1-29ae-4362-874b- 83f5b103727f.aspx)。
Just wanted to throw in my opinion (I'm the author of the MvcContrib grid component).
This is definitely language abuse - no doubt about it. However, I wouldn't really consider it counter intuitive - when you look at a call to
Attributes(style => "width:100%", @class => "foo")
I think it's pretty obvious what's going on (It's certainly no worse than the anonymous type approach). From an intellisense perspective, I agree it is pretty opaque.
For those interested, some background info on its use in MvcContrib...
I added this to the grid as a personal preference - I do not like the use of anonymous types as dictionaries (having a parameter that takes "object" is just as opaque as one that takes params Func[]) and the Dictionary collection initializer is rather verbose (I am also not a fan of verbose fluent interfaces, eg having to chain together multiple calls to an Attribute("style", "display:none").Attribute("class", "foo") etc)
If C# had a less verbose syntax for dictionary literals, then I wouldn't have bothered including this syntax in the grid component :)
I also want to point out that the use of this in MvcContrib is completely optional - these are extension methods that wrap overloads that take an IDictionary instead. I think it's important that if you provide a method like this you should also support a more 'normal' approach, eg for interop with other languages.
Also, someone mentioned the 'reflection overhead' and I just wanted to point out that there really isn't much of an overhead with this approach - there is no runtime reflection or expression compilation involved (see http://blog.bittercoder.com/PermaLink,guid,206e64d1-29ae-4362-874b-83f5b103727f.aspx).
我希望
它更加明确和标准,并且使用 lambda 不会获得任何好处。
I would prefer
It's much more explicit and standard and nothing is being gained by using lambdas.
欢迎来到 Rails Land :)
只要您知道发生了什么,就真的没有什么问题。 (当这种事情没有得到很好的记录时,就会出现问题)。
整个 Rails 框架都是基于约定优于配置的理念构建的。以某种方式命名事物可以让您了解它们正在使用的约定,并且您可以免费获得很多功能。遵循命名约定可以让您更快地到达目的地。整个事情运作得非常出色。
我见过这样的技巧的另一个地方是 Moq 中的方法调用断言。您传入一个 lambda,但该 lambda 永远不会被执行。他们只是使用表达式来确保方法调用发生,如果没有发生则抛出异常。
Welcome To Rails Land :)
There is really nothing wrong with it as long as you know what's going on. (It's when this kind of thing isn't documented well that there is a problem).
The entirety of the Rails framework is built on the idea of convention over configuration. Naming things a certain way keys you into a convention they're using and you get a whole lot of functionality for free. Following the naming convention gets you where you're going faster. The whole thing works brilliantly.
Another place where I've seen a trick like this is in method call assertions in Moq. You pass in a lambda, but the lambda is never executed. They just use the expression to make sure that the method call happened and throw an exception if not.
这在多个层面上都是可怕。不,这与 Ruby 完全不同。这是对 C# 和 .NET 的滥用。
关于如何以更直接的方式做到这一点,已经有很多建议:元组、匿名类型、流畅的接口等等。
它之所以如此糟糕,是因为它只是为了自己的利益而幻想:
当您需要从 Visual Basic 调用它时会发生什么?
.Attributes(Function(style) "width:100%")
这完全违反直觉,并且智能感知对于弄清楚如何传递内容几乎没有帮助。
它的效率不必要地低。
没有人知道如何维护它。
属性的参数类型是什么?是
Func
吗?这种意图是如何显露出来的?您的智能感知文档会说什么“请忽略对象的所有值”?我认为你有这种厌恶感是完全有道理的。
This is horrible on more than one level. And no, this is nothing like Ruby. It's an abuse of C# and .NET.
There have been many suggestions of how to do this in a more straightforward way: tuples, anonymous types, a fluent interface and so on.
What makes it so bad is that its just way to fancy for its own good:
What happens when you need to call this from Visual Basic?
.Attributes(Function(style) "width:100%")
It's completely counter intuitive, and intellisense will provide little help figuring out how to pass stuff in.
It's unnecessarily inefficient.
Nobody will have any clue how to maintain it.
What is the type of the argument going in to attributes? is it
Func<object,string>
? How is that intention revealing? What is your intellisense documentation going to say, "Please disregard all values of object"?I think you are completely justified having those feelings of revulsion.
我属于“语法才华”阵营,如果他们清楚地记录它,并且看起来非常酷,那么在我看来几乎没有问题!
I'm in the "syntax brilliance" camp, if they document it clearly, and it looks this freaking cool, there's almost no problem with it imo!
他们两个。这是对 lambda 表达式 AND 语法才华的滥用。
Both of them. It's abusage of lambda expressions AND syntax brilliance.
我几乎没有遇到过这种用法。我认为这是“不合适的”:)
这不是一种常见的使用方式,它不符合一般约定。这种语法当然有优点和缺点:
缺点
优点
底线 - 在公共 API 设计中我会选择更明确的方式。
I hardly ever came across this kind of usage. I think it's "inappropriate" :)
This is not a common way of use, it is inconsistent with the general conventions. This kind of syntax has pros and cons of course:
Cons
Pros
Bottom line - in public API design I would have chosen more explicit way.
不,这当然不是常见做法。这是违反直觉的,无法仅查看代码来弄清楚它的作用。你必须知道它是如何使用的才能理解它是如何使用的。
与使用委托数组提供属性不同,链接方法会更清晰且性能更好:
虽然这需要输入更多内容,但它清晰且直观。
No, it's certainly not common practice. It's counter-intuitive, there is no way of just looking at the code to figure out what it does. You have to know how it's used to understand how it's used.
Instead of supplying attributes using an array of delegates, chaining methods would be clearer and perform better:
Although this is a bit more to type, it's clear and intuitive.
我可以用它来创造一个短语吗?
magic lambda (n):仅用于替换魔术字符串的 lambda 函数。
Can I use this to coin a phrase?
magic lambda (n): a lambda function used solely for the purpose of replacing a magic string.
以下内容有什么问题:
What's wrong with the following:
所有这些关于“可怕”的咆哮都是一群长期使用 C# 的人反应过度(我是一名长期使用 C# 的程序员,并且仍然是该语言的忠实粉丝)。这种语法没有什么可怕的。它只是试图使语法看起来更像您想要表达的内容。语法中的“噪音”越少,程序员就越容易理解它。减少一行代码中的噪音只会有一点帮助,但如果让它在越来越多的代码中累积起来,结果就会带来巨大的好处。
作者试图争取 DSL 为您带来的相同好处——当代码“看起来像”您想要表达的内容时,您就已经到达了一个神奇的地方。您可以争论这是否有利于互操作,或者它是否比匿名方法更好以证明某些“复杂性”成本的合理性。很公平......所以在你的项目中你应该正确选择是否使用这种语法。但尽管如此……这仍然是程序员的一次聪明尝试,目的是做我们都在努力做的事情(无论我们是否意识到)。我们都在努力做的是:“用尽可能接近我们对计算机想要做什么的想法的语言告诉计算机我们想要它做什么。”
更接近以我们内部认为的相同方式向计算机表达我们的指令是使软件更易于维护和更准确的关键。
编辑:我说过“让软件更可维护、更准确的关键”,这是一种过于天真、夸大的独角兽。我把它改为“一把钥匙”。
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."
Getting closer to expressing our instructions to computers in the same manner that we think internally is a key to making software more maintainable and more accurate.
EDIT: I had said "the key to making software more maintainable and more accurate", which is a crazily naive overstated bit of unicorniness. I changed it to "a key."
这是表达式树的好处之一 - 人们可以检查代码本身以获取额外信息。这就是
.Where(e => e.Name == "Jamie")
可以转换为等效 SQLWhere 子句的方式。这是表达式树的巧妙使用,尽管我希望它不会比这更进一步。任何更复杂的东西都可能比它希望替换的代码更困难,所以我怀疑它会自我限制。This is one of the benefits of expression trees - one can examine the code itself for extra information. That is how
.Where(e => e.Name == "Jamie")
can be converted into the equivalent SQL Where clause. This is a clever use of expression trees, though I would hope that it does not go any further than this. Anything more complex is likely to be more difficult than the code it hopes to replace, so I suspect it will be self limiting.这是一个有趣的方法。如果您将表达式的右侧限制为常量,那么您可以使用
“我认为您真正想要的”而不是委托来实现(您正在使用 lambda 来获取两侧的名称)
请参阅下面的简单实现:
这甚至可能解决线程前面提到的跨语言互操作问题。
It is an interesting approach. If you constrained the right hand side of the expression to be constants only then you could implementing using
Which I think is what you really want instead of the delegate (you're using the lambda to get names of both sides)
See naive implementation below:
This might even address the cross language interop concern that was mentioned earlier in the thread.
该代码非常聪明,但它可能会导致它解决的更多问题。
正如您所指出的,参数名称(样式)和 HTML 属性之间现在存在模糊的依赖关系。不进行编译时检查。如果参数名称输入错误,页面可能不会有运行时错误消息,但更难发现逻辑错误(没有错误,但行为不正确)。
更好的解决方案是拥有一个可以在编译时检查的数据成员。因此,不是这样:
编译器可以检查具有 Style 属性的代码:
或者甚至:
这对代码作者来说是更多的工作,但这种方法利用了 C# 强大的类型检查能力,这有助于防止错误进入代码首先。
The code is very clever, but it potentially causes more problems that it solves.
As you've pointed out, there's now an obscure dependency between the parameter name (style) and an HTML attribute. No compile time checking is done. If the parameter name is mistyped, the page probably won't have a runtime error message, but a much harder to find logic bug (no error, but incorrect behavior).
A better solution would be to have a data member that can be checked at compile time. So instead of this:
code with a Style property could be checked by the compiler:
or even:
That's more work for the authors of the code, but this approach takes advantage of C#'s strong type checking ability, which helps prevent bugs from getting into code in the first place.
事实上,它看起来像 Ruby =),至少对我来说,使用静态资源进行以后的动态“查找”不适合 api 设计考虑,希望这个聪明的技巧在该 api 中是可选的。
当您不需要添加键来设置值时,我们可以从 IDictionary 继承(或不继承)并提供一个行为类似于 php 数组的索引器。这将是 .net 语义的有效使用,而不仅仅是 C#,并且仍然需要文档。
希望这有帮助
indeed its seems like Ruby =), at least for me the use of a static resource for a later dynamic "lookup" doesn't fit for api design considerations, hope this clever trick is optional in that api.
We could inherit from IDictionary (or not) and provide an indexer that behaves like a php array when you dont need to add a key to set a value. It will be a valid use of .net semantics not just c#, and still need documentation.
hope this helps
在我看来,这是对 lambda 的滥用。
至于语法的精妙之处,我发现
style=>"width:100%"
很令人困惑。特别是因为=>
而不是=
In my opinion it is abuse of the lambdas.
As to syntax brilliance i find
style=>"width:100%"
plain confusing. Particularily because of the=>
instead of=
恕我直言,这是一种很酷的方法。我们都喜欢这样一个事实:命名一个类 Controller 将使其成为 MVC 中的控制器,对吗?因此,在某些情况下,命名确实很重要。
这里的意图也非常明确。很容易理解
.Attribute( book => "something")
将导致book="something"
和.Attribute( log => ; "something")
会导致log="something"
我想如果你把它当作惯例的话,这应该不是问题。我认为,任何能让你编写更少的代码并使意图明显的事情都是一件好事。
IMHO, it is a cool way of doing it. We all love the fact that naming a class Controller will make it a controller in MVC right? So there are cases where the naming does matter.
Also the intention is very clear here. It is very easy to understand that
.Attribute( book => "something")
will result inbook="something"
and.Attribute( log => "something")
will result inlog="something"
I guess it should not be a problem if you treat it like a convention. I am of the opinion that whatever makes you write less code and makes the intention obvious is a good thing.
如果方法(func)名称选择得当,那么这是避免维护麻烦的绝妙方法(即:添加一个新函数,但忘记将其添加到函数参数映射列表中)。当然,您需要大量记录它,并且最好从该类中的函数的文档中自动生成参数的文档......
If the method (func) names are well chosen, then this is a brilliant way to avoid maintenance headaches (ie: add a new func, but forgot to add it to the function-parameter mapping list). Of course, you need to document it heavily and you'd better be auto-generating the documentation for the parameters from the documentation for the functions in that class...
我认为这并不比“魔弦”更好。为此,我也不太喜欢匿名类型。它需要一个更好的&强类型方法。
I think this is no better than "magic strings". I'm not much of a fan of the anonymous types either for this. It needs a better & strongly typed approach.