web-dev-qa-db-ja.com

割り当てステートメントが値を返すのはなぜですか?

これは許可されています:

int a, b, c;
a = b = c = 16;

string s = null;
while ((s = "Hello") != null) ;

私の理解では、割り当てs = ”Hello”;“Hello”sに割り当てるだけですが、操作は値を返すべきではありません。それが当てはまる場合、nullは何とも比較されないため、((s = "Hello") != null)はエラーを生成します。

割り当てステートメントが値を返すことを許可する背後にある理由は何ですか?

123
user437291

私の理解では、代入s = "Hello";に「Hello」が割り当てられるだけで、操作は値を返しません。

あなたの理解は100%間違っています。 この間違ったことを信じる理由を説明できますか?

割り当てステートメントが値を返すことを許可する背後にある理由は何ですか?

まず、代入statementsは値を生成しません。割り当ては値を生成します。割り当て式は有効なステートメントです。 C#で有効なステートメントである式はほんの一握りです。式の待機、インスタンスの構築、インクリメント、デクリメント、呼び出し、および割り当て式は、ステートメントが予想される場所で使用できます。

C#には、ある種の値を生成しない1種類の式、つまりvoidを返すように入力されたものの呼び出しのみがあります。 (または、同等に、結果値が関連付けられていないタスクの待機。)他のすべての種類の式は、値または変数または参照またはプロパティアクセスまたはイベントアクセスなどを生成します。

ステートメントとして有効なすべての式は副作用に有用であることに注意してください。これがここでの重要な洞察であり、おそらく割り当ては表現ではなくステートメントであるべきだという直感の原因だと思います。理想的には、ステートメントごとに厳密に1つの副作用があり、式に副作用はありません。 It is副作用のコードを式コンテキストで使用できるのは少し奇妙です。

この機能を許可する背後にある理由は、(1)頻繁に便利であり、(2)Cのような言語では慣用的であるためです。

質問が懇願されていることに気づくかもしれません:なぜこれはCのような言語で慣用的ですか?

残念ながら、デニスリッチーはもはや質問することはできませんが、私の推測では、割り当てはほとんど常に割り当てられたばかりの値を残しています。 Cは非常に「機械に近い」種類の言語です。もっともらしいし、Cの設計に沿って、基本的には「割り当てたばかりの値を使い続ける」ことを意味する言語機能があると思われます。この機能のコードジェネレーターを作成するのは非常に簡単です。割り当てられた値を保存したレジスタを使用し続けるだけです。

151
Eric Lippert

答えてくれませんか?あなたが言及したコンストラクトの種類を正確に有効にすることです。

代入演算子のこのプロパティが使用される一般的なケースは、ファイルから行を読み取ることです...

string line;
while ((line = streamReader.ReadLine()) != null)
    // ...
43
Timwi

割り当て式の私のお気に入りの使用方法は、遅延初期化されたプロパティです。

private string _name;
public string Name
{
    get { return _name ?? (_name = ExpensiveNameGeneratorMethod()); }
}
34
Nathan Baulch

たとえば、次の例のように、割り当てを連鎖させることができます。

a = b = c = 16;

別の方法として、単一の式で結果を割り当ててチェックすることができます:

while ((s = foo.getSomeString()) != null) { /* ... */ }

どちらもおそらく疑わしい理由ですが、これらの構成が好きな人は間違いなくいます。

27
Michael Burr

既に述べた理由(代入チェーン、whileループ内での設定とテストなど)以外に、 適切にusingステートメントを使用するには、この機能が必要です。

using (Font font3 = new Font("Arial", 10.0f))
{
    // Use font3.
}

MSDNは、処分された後でもスコープ内に残るため、usingステートメントの外で使い捨てオブジェクトを宣言することを推奨していません(リンク先のMSDN記事を参照)。

14

Eric Lippertが答えた特定のポイントについて詳しく説明し、他の誰にも触れられていない特定の機会にスポットライトを当てたいと思います。エリックは言った:

[...]ほとんどの場合、割り当てはレジスタに割り当てられたばかりの値を残します。

代入では、常に左オペランドに代入しようとした値が残ることになります。 「ほぼ常に」だけではありません。しかし、この問題がドキュメントでコメントされていないので、私は知りません。理論的には、左のオペランドを再評価せずに「残す」ことは非常に効果的な実装手順ですが、効率的ですか?

このスレッドの回答で構築されたこれまでのすべての例では、「効率的」です。しかし、get-およびsetアクセサーを使用するプロパティおよびインデクサーの場合は効率的ですか?どういたしまして。次のコードを検討してください。

class Test
{
    public bool MyProperty { get { return true; } set { ; } }
}

ここには、プライベート変数のラッパーではないプロパティがあります。呼びかけられたときはいつでも真を返し、値を設定しようとしたときは何もしません。したがって、この特性が評価されるときはいつでも、彼は真実です。しばらく様子を見てみましょう:

Test test = new Test();

if ((test.MyProperty = false) == true)
    Console.WriteLine("Please print this text.");

else
    Console.WriteLine("Unexpected!!");

何が印刷されると思いますか? Unexpected!!を出力します。結局のところ、セットアクセサーは実際に呼び出されますが、何も実行されません。ただし、その後、getアクセサーが呼び出されることはありません。割り当ては、プロパティに割り当てようとしたfalse値をそのまま残します。そして、このfalse値はifステートメントが評価するものです。

実際の例で締めくくりますそれで、この問題を調査しました。私のクラスがプライベート変数として持っているコレクション(List<string>)の便利なラッパーであるインデクサーを作成しました。

インデクサーに送信されたパラメーターは文字列であり、これはコレクション内の値として扱われます。 getアクセサーは、その値がリストに存在するかどうかにかかわらず、trueまたはfalseを返すだけです。したがって、getアクセサーはList<T>.Containsメソッドを使用する別の方法でした。

インデクサーのセットアクセサーが文字列を引数として呼び出され、右側のオペランドがbool trueであった場合、彼はそのパラメーターをリストに追加します。ただし、同じパラメーターがアクセサーに送信され、右側のオペランドがbool falseである場合、代わりにリストから要素を削除します。したがって、セットアクセサは、List<T>.AddList<T>.Removeの両方の便利な代替手段として使用されました。

ゲートウェイとして実装された独自のロジックでリストをラップするきちんとしたコンパクトな「API」があると思った。インデクサーだけの助けを借りて、いくつかのキーストロークで多くのことができました。たとえば、リストに値を追加して、そこに値があることを確認するにはどうすればよいですか?私はこれが必要なコードの唯一の行だと思った:

if (myObject["stringValue"] = true)
    ; // Set operation succeeded..!

しかし、前の例で示したように、値が実際にリストにあるかどうかを確認するgetアクセサーは呼び出されませんでした。 true値は、getアクセサーで実装したロジックを効果的に破棄するために常に残されていました。

9

代入が値を返さなかった場合、_a = b = c = 16_行も機能しません。

また、while ((s = readLine()) != null)のようなものを書くことができると便利な場合があります。

そのため、割り当てに割り当てられた値を返させる理由は、これらのことをできるようにするためです。

6
sepp2k

パーサーがその構文をどのように解釈するかを誤解していると思います。割り当てはfirstで評価され、結果はNULLと比較されます。つまり、ステートメントは次と同等です。

s = "Hello"; //s now contains the value "Hello"
(s != null) //returns true.

他の人が指摘したように、割り当ての結果は割り当てられた値です。持っていることの利点を想像するのは難しいと思う

((s = "Hello") != null)

そして

s = "Hello";
s != null;

not同等である...

4
Dan J

主な理由は、C++とCとの(意図的な)類似性だと思います。アサイグメント演算子(および他の多くの言語構成要素)をC++の対応物のように動作させることは、最小限の驚きの原則に従い、他のカーリーから来たプログラマーはブラケット言語は、あまり考えずにそれらを使用できます。 C++プログラマーにとって簡単に習得できることは、C#の主な設計目標の1つです。

3
tdammers

投稿に含める2つの理由
1)あなたは_a = b = c = 16_を行うことができます
2)したがって、割り当てが成功したかどうかをテストできますif ((s = openSomeHandle()) != null)

2
JamesMLV

「a ++」または「printf( "foo")」は、自己完結型ステートメントとして、またはより大きな式の一部として有用である可能性があるということは、Cが式の結果がそうである場合とそうでない可能性を考慮しなければならないことを意味します中古。それを考えると、値を有効に「戻す」式も同様にそうするという一般的な概念があります。問題のすべての変数が正確に同じ型を持たない場合、割り当て連鎖はCでわずかに「興味深い」場合があり、C++ではさらに興味深い場合があります。このような使用はおそらく避けるのが最善です。

1
supercat

別の優れた使用例、私はこれを常に使用しています:

var x = _myVariable ?? (_myVariable = GetVariable());
//for example: when used inside a loop, "GetVariable" will be called only once
1
jazzcat

ここでの回答にはない追加の利点は、割り当ての構文が算術に基づいていることです。

x = y = b = c = 2 + 3は、算術的にCスタイル言語とは異なるものを意味します。算術ではアサーションで、xはyなどに等しいというstateであり、Cスタイル言語ではmakes xがyに等しいなどの命令です実行されます。

これは、正当な理由がない限り、算術とコードの間にはまだ十分な関係があるため、算術で自然なことを禁止するのは理にかなっていないということです。 (Cスタイル言語が等号の使用から取った他のことは、等値比較のための==の使用です。ただし、右端の==は値を返すため、この種の連鎖は不可能です。)

0
Jon Hanna