パラメータ名の反映:C#ラムダ式の乱用または構文の優秀さ?


428

私はMvcContribグリッドコンポーネントを見て、グリッド構文で使用されている構文トリックに魅了されましたが、同時に反発しました。

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

上記の構文は、生成されたHTMLのスタイル属性をに設定しwidth:100%ます。注意を払えば、「スタイル」はどこにも指定されていません。式内のパラメーターの名前から推定されます!私はこれを掘り下げる必要があり、「魔法」が発生する場所を見つけました:

Hash(params Func<object, TValue>[] hash)
{
    foreach (var func in hash)
    {
        Add(func.Method.GetParameters()[0].Name, func(null));
    }
}

したがって、実際には、コードは正式なコンパイル時のパラメータ名を使用して、属性の名前と値のペアのディクショナリを作成しています。結果として得られる構文構成は実際に非常に表現力豊かですが、同時に非常に危険です。

ラムダ式の一般的な使用では、副作用なしで使用される名前の置換が可能です。私が言う本の中の例を参照してくださいcollection.ForEach(book => Fire.Burn(book))私は私のコードで書くことができます知っているcollection.ForEach(log => Fire.Burn(log))、それは同じことを意味します。しかし、ここでMvcContribグリッド構文を使用すると、突然、変数に選択した名前に基づいて積極的に調べて決定を行うコードが見つかります。

それで、C#3.5 / 4.0コミュニティとラムダ式愛好家とのこの一般的な習慣はありますか?または、私が心配する必要のない不正な1つのトリックマーベリックですか?


34
構文を単に解析するのではなく、コードの意図を確認するつもりであれば、これは明白に見えると私は主張します。あなたが良いコードを読んでいるなら、それはあなたがとにかくやるべきことです。構文は単なる意図の伝達手段であり、これは意図を明らかにするコードであると私は主張します。
ジェイミーペニー

191
Anders(および他の設計チーム)に彼らの考えを聞いたところです。結果が家族向けの新聞に印刷できないとだけ言ってみましょう。
Eric Lippert、

22
C#には現在、マップ、特に関数に渡されるマップのクリーンで軽量な構文がありません。さまざまな動的言語(Ruby、Python、JavaScript、ColdFusion 9)には、そのための簡潔で軽い構文があります。
yfeldblum 2009年

28
ああ、私はそれが楽しいと思います。マップの構文については、ええ、新しい{{"Do"、 "A deer"}、{"Re"、 "golden sun"} ...}を実行してコンパイラーに推測させることができれば素晴らしいでしょうnew [] {1、2,3}がint配列の構築を推測するのと同じように、マップの構築。このようなことを検討しています。
Eric Lippert、

17
エリックリッペルト、私はあなたを非常に尊敬していますが、あなたとグループ(私がFREAKIN 'TREMENDOUSLYを尊重しているアンダースを含む)はこれを厳しく非難していると思います。ご承知のとおり、C#にはマップの厳密な構文がなく、他の一部の言語(Rubyなど)には優れた言語があります。この男は、彼が望んだ構文を取得する方法を見つけました。彼の構文と同じくらい表現力があり、欠点が少ない同様の表現方法があることを認めます。しかし、彼の構文とそれを取得するために一生懸命働いたという事実は、言語拡張の必要性を明確に示しています。過去のパフォーマンスは、皆さんがそのために素晴らしいものを作成することを示しています。
チャーリーフラワーズ

回答:


147

これは相互運用が不十分です。たとえば、次の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言語間で適切な相互運用性を確保したい場合は、この種の「乱用」を回避するか、少なくとも「標準」のオーバーロード(たとえば、文字列名を追加パラメーターとして受け取る)を提供する必要があります。


11
あなたはしません。この戦略は単に移植不可能です。(それが役立つ場合は、例として、他の方法として、F#は戻り値の型のみが異なるメソッドをオーバーロードできる可能性があります(型の推論はそれを行うことができます)。これはCLRで表現できます。ただし、F#はこれを許可しません。そうした場合、これらのAPIはC#から呼び出すことができません。)相互運用性に関しては、「エッジ」機能には、得られるメリットとトレードオフする相互運用性に関して常にトレードオフがあります。
ブライアン

32
何かをしない理由として、相互運用性が嫌いです。相互運用性が要件である場合はそれを行い、そうでない場合は、なぜそれを心配する必要がありますか?YAGNI IMHOです。
ジョンファレル

15
@jfar:.NET CLRでは、任意のコンパイラで生成されたアセンブリは他の言語から消費されることが想定されているため、土地の相互運用性にはまったく新しい次元があります。
Remus Rusanu 2009

25
CLSに準拠している必要はないことに同意しますが、ライブラリまたはコントロール(およびこれを開始するスニペットがグリッドからのものである場合)を作成している場合は良い考えのようです。それ以外の場合は、制限しているだけです。オーディエンス/顧客ベース
JMarsch 2009

4
たぶんそれを変更する価値があります:Func <object、string> to Expression << Func <object、string >>そして、式の右側を定数だけに制約する場合、これを行う実装を持つことができます: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] =(string)((ConstantExpression)func.Body).Value; }戻り値。}
davidfowl 2009

154

私はその名前のせいでそれほど奇妙なのではなく、ラムダが不要であることを発見しました。匿名型を使用してより柔軟にすることができます:

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

これは(たとえば)ASP.NET MVCの多くで使用されるパターンであり、他の用途もあります警告、名前が呼び出し元固有ではなく魔法の値である場合のAyendeの考えに注意してください)


4
これには相互運用性の問題もあります。すべての言語がそのような匿名型のオンザフライでの作成をサポートしているわけではありません。
ブライアン、

5
誰も本当に質問に答えなかったのが好きですが、代わりに人々は「これは良い」という議論を提供します。:p乱用かどうか?
サムサフラン

7
私はこれに対するエリック・リペルトの反応を見てみたいです。なぜなら、それはフレームワークのコードにあります。そしてそれは同じくらい恐ろしいことです。
Matt Hinze、

26
コードが書かれたの問題は読みやすさではないと思います。本当の問題はコードの学習可能性だと思います。あなたのインテリセンスが.Attributes(object obj)と言ったとき、あなたはどう思いますか?メソッドに何を渡すべきかわからないので、(誰もしたくない)ドキュメントを読む必要があります。これは問題の例よりも実際には良いとは思いません。
NotDan 2009

2
@Arnis-より柔軟な理由:暗黙のパラメーター名に依存しないため、一部のラムダ実装(他の言語)で問題が発生する可能性があります(他の言語)-定義されたプロパティを持つ通常のオブジェクトを使用することもできます。たとえばHtmlAttributes、予想される属性(インテリセンス用)を持つクラスを作成し、null値を持つものは無視することができます...
マークグラベル

137

私の意見を投げたかっただけです(私はMvcContribグリッドコンポーネントの作成者です)。

これは間違いなく言語の乱用です-それについては間違いありません。ただし、私はそれを直感に反するものとは考えていませんAttributes(style => "width:100%", @class => "foo")
。インテリセンスの観点から、私はそれがかなり不透明であることに同意します。

興味がある人のために、MvcContribでの使用に関するいくつかの背景情報...

これを個人的な好みとしてグリッドに追加しました-辞書としての匿名型の使用( "オブジェクト"をとるパラメーターを持つことは、パラメーターFunc []をとるパラメーターと同じくらい不透明です)と、辞書コレクション初期化子がどちらかと言えば冗長です(私は冗長な流暢なインターフェイスのファンではありません。たとえば、Attribute( "style"、 "display:none")。Attribute( "class"、 "foo")などの複数の呼び出しをつなぎ合わせる必要があります)。

C#の辞書リテラルの冗長構文が少なければ、グリッドコンポーネントにこの構文を含めても問題はありませんでした:)

また、MvcContribでのこれの使用は完全にオプションであることを指摘しておきます。これらは、代わりにIDictionaryを取るオーバーロードをラップする拡張メソッドです。このようなメソッドを提供する場合、他の言語との相互運用など、より「通常の」アプローチもサポートすることが重要だと思います。

また、誰かが「リフレクションオーバーヘッド」について言及し、このアプローチではオーバーヘッドが実際にはそれほどないことを指摘したいと思います。ランタイムリフレクションや式のコンパイルは含まれません(http://blog.bittercoder.comを参照)/PermaLink,guid,206e64d1-29ae-4362-874b-83f5b103727f.aspx)。


1
また、私は私のブログにいくつかのより多くの深さで、ここで提起された問題のいくつかに対処しようとしました:jeremyskinner.co.uk/2009/12/02/lambda-abuse-the-mvccontrib-hash
ジェレミー・スキナー

4
Intellisenseでは、匿名オブジェクトと同じくらい不透明です。
Brad Wilson、

22
+1は、オプションの拡張メソッドを介してインターフェイスが追加されることを示します。C#を使用していないユーザー(および言語の乱用に腹を立てている人)は、使用を控えることができます。
–JørnSchou-Rode、2010

50

を好む

Attributes.Add(string name, string value);

これははるかに明示的で標準的であり、ラムダを使用しても何も得られません。


20
それはそうですか?(生成された実際のhtml)html.Attributes.Add("style", "width:100%");ほど読みやすくありませんが、結果のHTMLでの表示に非常に近くなります。style = "width:100%"style => "width:100%"
ジェイミーペニー

6
それらの構文は、.Attributes(id => 'foo'、@class => 'bar'、style => 'width:100%')のようなトリックを可能にします。関数シグネチャは、可変数の引数にparams構文を使用します。Attributes(params Func <object、object> [] args)。非常に強力ですが、wtfを理解するのにかなり時間がかかりました。
Remus Rusanu 2009

27
@ジェイミー:C#コードをHTMLコードのように見せようとすることは、設計上の決定の悪い理由になります。それらは完全に異なる目的のための完全に異なる言語であり、同じように見えるべきではありません。
Guffa

17
「美しさ」を犠牲にすることなく、匿名オブジェクトも使用された可能性がありますか?.Attributes(new {id = "foo"、@class = "bar"、style = "width:100%"})??
Funka

10
@Guffaなぜそれがデザイン決定の悪い理由になるのでしょうか?なぜ彼らは同じに見えないのですか?その推論によって、彼らは意図的に異なって見えるべきですか?私はあなたの間違ったことを言っているのではなく、あなたはあなたの主張をより完全に詳しく述べたいと思っているだけだと言っているだけです。
サマンサブラナム

45

Rails Landへようこそ:)

何が起こっているのかを知っている限り、問題はありません。(問題があるのは、この種のことが十分に文書化されていないときです)。

Railsフレームワーク全体は、構成よりも規約の考え方に基づいて構築されています。特定の方法で物事に名前を付けると、それらが使用している規則に合わせて、無料で多くの機能を利用できます。命名規則に従うと、より速く進むことができます。すべてが見事に機能します。

このようなトリックを見た別の場所は、Moqのメソッド呼び出しアサーションです。ラムダを渡しますが、ラムダは実行されません。式を使用して、メソッド呼び出しが発生したことを確認し、発生しなかった場合は例外をスローします。


3
私は少し躊躇しましたが、同意します。リフレクションのオーバーヘッドは別として、Add()のように文字列を使用する場合とラムダパラメーター名を使用する場合の間に大きな違いはありません。少なくとも私は思いつくことができます。両方に気づかずに、それをマックして「sytle」と入力できます。
サマンサブラナム

5
これがなぜ奇妙なことではないのか理解できず、Railsを思い出しました。:D
Allyn

43

これは複数のレベルで恐ろしいことです。いいえ、これはRubyに似ていません。C#と.NETの乱用です。

これをより簡単な方法で行う方法については、タプル、匿名型、流暢なインターフェースなど、多くの提案がありました。

何がそんなに悪いのかというと、それは自分の利益のために空想するための正しい方法です。

  • これをVisual Basicから呼び出す必要がある場合はどうなりますか?

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

  • それは完全に直観に反するものであり、インテリセンスはものを渡す方法を理解するのにほとんど役立ちません。

  • それは不必要に非効率的です。

  • 誰もそれを維持する方法の手がかりはありません。

  • 属性に入る引数のタイプは何ですか?それがありますかFunc<object,string>?その意図はどのように明らかになりますか?「オブジェクトのすべての値を無視してください」というIntellisenseのドキュメントにはどのような記述がありますか?

私はあなたがそれらの嫌悪感を持っていることを完全に正当化すると思います。


4
私は言うでしょう-それは完全に直感的です。:)
Arnis Lapsa 09

4
あなたはそれがRubyに似ていないと言います。しかし、ハッシュテーブルのキーと値を指定するためのRubyの構文に非常によく似ています。
チャーリーフラワーズ

3
アルファ変換で壊れるコード!イェイ!
Phil

3
@Charlie、構文的には似ていますが、意味的にはかなり異なります。
Sam Saffron

39

私は「構文の輝き」のキャンプにいます、彼らがそれを明確に文書化し、それがこのすごいクールに見えるなら、それはほとんど問題ありません!


10
アーメン、兄弟。アーメン(コメントの最小長を満たすために必要な2番目のアーメン:)
チャーリーフラワーズ

あなたのコメントだけでは、必要以上のことがありました。D:しかし、その後、あなたはあなたのコメントに置くことができない一度だけアーメン、その後
スリーパースミス


21

このような使い方をしたことはほとんどありません。「不適切」だと思います:)

これは一般的な使用方法ではなく、一般的な規則と一致していません。この種の構文には、もちろん長所と短所があります。

短所

  • コードは直感的ではありません(通常の規則は異なります)
  • 壊れやすい傾向があります(パラメーターの名前を変更すると機能が損なわれます)。
  • テストは少し難しいです(APIを偽造するには、テストでリフレクションを使用する必要があります)。
  • 式を集中的に使用すると、値(反射コスト)だけでなくパラメーターを分析する必要があるため、遅くなります

長所

  • これは、開発者がこの構文に合わせた後の方が読みやすくなっています。

結論 -パブリックAPI設計では、より明示的な方法を選択したでしょう。


2
@エリシャ-あなたの長所と短所が逆になります。少なくとも、プロが「直感的でない」コードを持っていると言っているのではないでしょうか。;-)
メトロスマーフ

この特定のケースでは、ラムダパラメータ名と文字列パラメータはどちらも壊れやすいものです。これは、xml解析に動的を使用するのに似ています-とにかくxmlについて確信が持てないため、適切です。
Arnis Lapsa、2009

19

いいえ、それは確かに一般的な習慣ではありません。直感に反し、コードを見てそれが何をするのかを理解する方法はありません。それがどのように使用されるかを理解するには、それがどのように使用されるかを知る必要があります。

デリゲートの配列を使用して属性を提供する代わりに、メソッドのチェーンがより明確になり、パフォーマンスが向上します。

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

これはタイプするのが少し多いですが、それは明確で直感的です。


6
本当に?私はそれを見て、そのコードスニペットが何を意図しているかを正確に知っていました。あなたが非常に厳格でない限り、それは鈍いことではありません。文字列連結の+のオーバーロードについて同じ引数を与えることができ、代わりに常にConcat()メソッドを使用する必要があります。
サマンサブラナム

4
@スチュアート:いいえ、正確にはわかりませんでした。使用した値に基づいて推測しただけです。誰もがその推測をすることができますが、推測はコードを理解する良い方法ではありません。
Guffa

11
を使用し.Attribute("style", "width:100%")て私style="width:100%"に与えることを推測しているが、すべてのためにそれが私に与えることができることを知っているfoooooo。違いはわかりません。
サマンサブラナム

5
「使用された値に基づく推測」は、常にコードを見るときに行うことです。stream.close()の呼び出しに遭遇した場合、ストリームを閉じると想定しますが、まったく別のことを行うこともできます。
Wouter Lievens、

1
@Wouter:コードの読み取り時に常に推測していると、コードの読み取りが非常に困難になるはずです... "close"という名前のメソッドの呼び出しを見つけたら、クラスの作成者が命名規則について知らないことがわかります。 、それで、私はメソッドが何をするかについて当たり前のことを何でも取るのを非常にためらいます。
Guffa

17

これを使用してフレーズを作成できますか?

magic lambda(n):マジック文字列を置き換える目的でのみ使用されるラムダ関数。


ええ...それは面白いです。おそらくそれは魔法のようなものかもしれません、コンパイル時の安全性がないという意味で、この使用がコンパイル時エラーではなくランタイムを引き起こす場所はありますか?
Maslow


16

この「恐ろしさ」についてのすべての怒りは、長年のc#の人の過剰反応です(そして私は長年のC#プログラマーであり、言語の非常に大きなファンです)。この構文に恐ろしいことは何もありません。これは、構文を表現しようとしているものに似せようとする試みにすぎません。構文の「ノイズ」が少ないほど、プログラマはそれを理解しやすくなります。1行のコードでノイズを減らすことは少ししか役に立ちませんが、ますます多くのコードにまたがって蓄積していくと、かなりの利益になります。

これは、DSLが提供するのと同じ利点を追求するための作者による試みです。これが相互運用に適しているのか、それとも「複雑さ」のコストの一部を正当化するのに匿名メソッドよりも十分に優れているのかについて、議論することができます。十分に公正です...したがって、プロジェクトでは、この種の構文を使用するかどうかを適切に選択する必要があります。しかし、それでも...これはプログラマーが賢明な試みであり、結局のところ、私たちは皆、それを実現しようとしている(実現したかどうかにかかわらず)のです。そして、私たちがやろうとしているのはこれです。「コンピュータに何をしてほしいかを、コンピュータが何をしたいかについて考えている方法にできるだけ近い言語で伝えます。」

社内で考えるのと同じ方法でコンピューターへの指示を表現することに近づくことは、ソフトウェアをより保守可能かつ正確にするための鍵です。

編集:私は「ソフトウェアをより保守可能でより正確にするための鍵」と言っていました、それは狂ったように素朴で誇張されたユニコーン性のビットです。「鍵」に変更しました。


12

これは式ツリーの利点の1つです。追加情報についてコード自体を調べることができます。これは.Where(e => e.Name == "Jamie")、同等のSQL Where句に変換する方法です。これは表現ツリーの巧妙な使用法ですが、これ以上先に進まないようにしたいと思います。より複雑なものは、置き換えたいコードよりも難しくなる可能性が高いので、自己制限的ではないかと思います。


これは有効なポイントですが、宣伝の真実です。LINQには、これをより正当なものにするTableAttributeやColumnAttributeなどの属性のセット全体が付属しています。また、linqマッピングは、クラス名とプロパティ名を調べます。これらは、パラメーター名よりも間違いなく安定しています。
Remus Rusanu 2009年

そこに同意します。Eric Lippert / Anders Helsberg /等がこの件に関して言ったことを読んだ後、私はこれについて私のスタンスを少し変えました。それはまだいくらか役立つので、私はこの答えを残しておくと思いました。それだけの価値があるので、私は今、このHTMLでの作業のスタイルは良いと思いますが、言語に適合しません。
ジェイミーペニー

7

それは興味深いアプローチです。式の右辺を定数のみに制約した場合、次を使用して実装できます

Expression<Func<object, string>>

これは、デリゲートの代わりに本当に必要なものだと思います(ラムダを使用して両側の名前を取得しています)以下の単純な実装を参照してください。

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#の強力な型チェック機能を利用しており、最初からバグがコードに侵入するのを防ぎます。


3
コンパイル時のチェックに感謝しますが、これは意見の問題に帰着すると思います。たぶんnew Attributes(){Style: "width:100%"}のようなものは、より簡潔なので、これにより多くの人々を獲得するでしょう。それでも、HTMLが許可するすべてを実装することは大きな仕事であり、strings / lambdas / anonymousクラスを使用しただけで誰かを責めることはできません。
サマンサブラ

5

実際、それはRuby =)のように見えます。少なくとも私にとって、後の動的な「ルックアップ」に静的リソースを使用することは、API設計の考慮事項に適合しません。このAPIでこの巧妙なトリックがオプションであることを願っています。

IDictionaryから継承するかどうかにかかわらず、値を設定するためにキーを追加する必要がない場合は、php配列のように動作するインデクサーを提供できます。これは、c#だけでなく.netセマンティクスの有効な使用方法であり、ドキュメントも必要です。

お役に立てれば


4

私の意見では、それはラムダの乱用です。

構文の輝きに関しては、私はstyle=>"width:100%"明白に混乱します。特にの=>代わりに=


4

私見、それはそれを行うクールな方法です。クラスコントローラーに名前を付けると、MVCのコントローラーになるということは誰もが気に入っています。したがって、命名が重要な場合があります。

ここでの意図も非常に明確です。その.Attribute( book => "something")結果、book="something"そして結果的になることを理解することは非常に簡単.Attribute( log => "something")ですlog="something"

慣習のように扱っても問題ないと思います。私は、コードを少なくして意図を明らかにすることは何でも良いことだと思います。


4
クラスに名前を付けても、コントローラから継承しない場合、スクワットは行われません...
ジョーダンウォールワーク

3

メソッド(func)名が適切に選択されている場合、これはメンテナンスの頭痛を回避するための優れた方法です(つまり、新しいfuncを追加しますが、関数とパラメーターのマッピングリストに追加するのを忘れています)。もちろん、それを詳細に文書化する必要があり、そのクラスの関数のドキュメントからパラメーターのドキュメントを自動生成する方がよいでしょう...


1

これは「マジックストリング」に勝るとは思えません。私はこれについても、匿名タイプのファンではありません。それは、より良く強く型付けされたアプローチを必要とします。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.