私はしばしば次のパターンに遭遇します。
while(GetValue(i) != null)
{
DoSomethingWith(GetValue(i++));
}
ここでGetValue
は2回実行されます。 GetValue
の結果を評価して保存できるパターンを使用するほうがはるかに便利です。場合によっては(ストリーム)、値を2回取得することが不可能です(回避策については、以下の代替案を参照してください)。使用できるパターンまたはループ構造はありますか?
私が自分の欠点で考えたいくつかの代替案。
代替1
// Variable outside of loop scope, extra if
var value = null;
do
{
value = GetValue(i++);
if(value != null) { DoSomethingWith(value); }
} while(value != null);
代替2
// Two get values, variable outside of loop
var value = GetValue(i);
while(value != null)
{
DoSometingWith(value);
value = GetValue(++i);
}
代替
// Out only works on reference types, most enumerables do not have
// a TryGet like method so we need to create our own wrapper
Object value;
while(TryGetValue(i++, out value))
{
DoSomethingWith(value);
}
理想的な世界のシナリオ(無効なC#)
while((var value = GetValue(i++)) != null)
{
DoSomethingWith(value);
}
以下は有効なC#です
public static void Main()
{
var i = 0;
string value;
while ((value = GetValue(i++)) != null)
{
DoSomethingWith(value);
}
}
private static string GetValue(int input)
{
if (input > 20)
{
return null;
}
return input.ToString();
}
private static void DoSomethingWith(string value)
{
Console.WriteLine(value);
}
「理想的な世界のシナリオ」と比較してください。
var i = 0;
while((var value = GetValue(i++)) != null)
{
DoSomethingWith(value);
}
私がしなければならなかったのは、value
をループの外に引っ張ることだけでした。提示されたすべての代替案について、これを喜んで行うようです。したがって、私はこれがあまりにも長すぎるとは思わない。
私は代替案で遊んでいます...ここに別のものがあります:
var i = 0;
while (true)
{
var value = GetValue(i++);
if (value == null)
{
break;
}
DoSomethingWith(value);
}
この場合、value
をループ内で宣言して(var
を使用して)、null
を2回確認する必要はありません。同じ入力でGetValue
を2回呼び出す必要はなく、作成する必要もありません。 out
パラメーターを持つラッパー。
for
ループを使用して同じことを表現しようとする場合があります。ただし、素朴なアプローチは機能しません。
// I repeat, this does not work:
var i = 0;
for (string value = null; value != null; value = GetValue(i++))
{
DoSomethingWith(value);
}
このバージョンの問題は、value
がnull
であることで始まり、終了基準を満たしているため、反復処理が行われないことです。
JimmyJamesが指摘 のように、次のように記述できます。
var i = 0;
for(string value; (value = GetValue(i++)) != null;)
{
DoSomethingWith(value);
}
私が目にする唯一の欠点は、型を記述する必要があることです(var
は使用できません)。
補遺:これは別のバリアントです Maliafoによって提案されました :
var i = 0;
for (var value = GetValue(i); value != null; value = GetValue(++i))
{
DoSomethingWith(value);
}
このバージョンではGetValue
を書き込む必要がありますが、同じ値で2回呼び出すことはありません。
スコープでもi
を宣言しますか?これを見てください:
for (var i = 0; ; i++)
{
var value = GetValue(i);
if (value == null)
{
break;
}
DoSomethingWith(value);
}
これは、私が「無限」で見たループと同じですが、私が上に投稿したソリューションです。それでも、しばらくの間条件がなかったので... for
に変更してi
をインクリメントするのはなぜですか?
Addendum:C#7.0機能の興味深い使用法については NetMageの回答 も参照してください。
C#7.0では、次のように(ab)使用できます。
while (GetValue(i++) is var value && value != null)
DoSomethingWith(value);
別のC#7.0機能を使用して、拡張メソッドを作成できます。
public static T As<T>(this T val, out T newvar) => newvar = val;
そして、out
を犠牲にして、変数を作成し、値をテストします。
while (GetValue(i++).As(out var value) != null) {
DoSomethingWith(value);
または、理想的な世界シナリオをエミュレートするバリエーションを使用します。
public static T Let<T>(out T newvar, T val) => newvar = val;
while (Let(out var value, GetValue(i++)) != null) {
DoSomethingWith(value);
関数型プログラミングの脚本から一枚を取り出します。コードの構造に繰り返しパターンがある場合は、同じままの部分を含む関数を作成し、変化する部分を関数パラメーター(デリゲート、 C#)。そうすれば、それがどれほど醜いかは関係ありません。一度だけ書いて、どこかの図書館でそれを隠す必要があります。
(あなたが私にJavaここに落ちることを許す必要があります;私のC#スキルは少し錆びています)
public static <T> void untilNull (Function<Integer,T> supplier, int i, Action<T> action) {
T v;
while ((v = supplier.apply(i++)) != null)
action.perform(v);
}
....
untilNull (this::getValue, 1, this::doSomethingWithValue);
更新-C#バージョン:
public static <T> void UntilNull (Func<int,T> supplier, int i, Action<T> action) {
T v;
while ((v = supplier(i++)) != null) action(v);
}
....
UntilNull (this.GetValue, 1, this.DoSomethingWithValue);
この解決策は、問題を解決するために最小限のコードが必要な場合、代替案1および2よりも確実に優れています。
while(true){
var value = GetValue(i++);
if(value != null) {
DoSomethingWith(value);
}
else{
break;
}
}
私の意見では、最もクリーンで一般的なソリューションの場合、このイテレータ function parameter ベースのソリューションを使用するのが最適です。 3番目のオプションによって提供される特定のラッパーと重複コードをクラス化する必要はなく、このようなすべての問題に対して機能します(そのため、短いインラインのwhileループ実装よりも優れていると思います)。さらに良いことに、とにかくイテレータを使用する場合は、イテレータ自体にnullcheckを入れてみませんか?これにより、位置を取得してnullを返すことができるほぼすべての関数にこれを使用できます。
public static System.Collections.Generic.IEnumerable<T>
FunctionSequence(Func<int,T> f)
{
int i = 0;
while(true){
value = f(i++);
if(value != null) {
yield return value;
}
else{
break;
}
}
}
foreach(GetValueType value in FunctionSequence<GetValueType>(GetValue)){
DoSomethingWith(value);
}
すべての関数にボイラープレートコードが必要ないことに注意してくださいFunctionSequence
イテレータ、最初のnull要素まで反復します。これを書くのは一度だけで、これは
0から始めたくない場合は、常に関数のシグネチャを次のように変更できます。
public static System.Collections.Generic.IEnumerable<T>
FunctionSequence(Func<int,T> f, int start = 0)
{
int i = start;
...
また、チェックが何であるかを変更することもできます。それがnullであるか、特定のオブジェクトが返されたかです。
public static System.Collections.Generic.IEnumerable<T>
FunctionSequence(Func<int,T> f, int start = 0, T terminator = null)
{
int i = start;
while(true){
value = f(i++);
if(value != terminator) {
...
これはあなたの場合に最適なソリューションだと思います。
私はC#ではなく、ほとんどがSwiftです。ほとんどの場合、私は書くでしょう
for value in <some collection> { ... }
これは構文糖
while let value = iterator.next() { }
(let value = expressionはexpressionを評価し、nilかどうかをチェックします。nilが存在する場合、nilでない場合は、オプションではない値をvalueに格納します。イテレータのnext()はオプションの値を返します。イテレータが終了した場合はnil )。
私は代替3を使用します。参照型で使用できる理由がわかりません。まず、参照型を使用していない場合は、nullを確認する必要はありません。 「これはenumに適切な値がありますか?」などの他の検証が必要な場合そのチェックをwhileで使用するだけで、適切な場合にのみ実行されます(または、宣言時に変数を初期化することを意味しますが、outの代わりにrefを使用できます)
代替案はどうですか4:
GetValueをIEnumerable/IEnumeratorにラップします。その後、理想的なシナリオを可能にする通常のforeach構文を使用できます。動的なコンテンツがある場合は、本格的なCollectionクラスを実装する必要はありません。yieldステートメントの周りのいくつかの関数でうまくいきます。
データでいくつかのLinq手品を使用することもできるはずです。