与えられた:
_class Program
{
private static readonly List<(int a, int b, int c)> Map = new List<(int a, int b, int c)>()
{
(1, 1, 2),
(1, 2, 3),
(2, 2, 4)
};
static void Main(string[] args)
{
var result = Map.FirstOrDefault(w => w.a == 4 && w.b == 4);
if (result == null)
Console.WriteLine("Not found");
else
Console.WriteLine("Found");
}
}
_
上記の例では、if (result == null)
行でコンパイラエラーが発生しています。
CS0019演算子 '=='は、タイプ '(int a、int b、int c)'および ''のオペランドには適用できません
「見つかった」ロジックに進む前に、タプルが見つかったことを確認するにはどうすればよいですか?
新しいc#7タプルを使用する前に、私はこれを持っているでしょう:
_class Program
{
private static readonly List<Tuple<int, int, int>> Map = new List<Tuple<int, int, int>>()
{
new Tuple<int, int, int> (1, 1, 2),
new Tuple<int, int, int> (1, 2, 3),
new Tuple<int, int, int> (2, 2, 4)
};
static void Main(string[] args)
{
var result = Map.FirstOrDefault(w => w.Item1 == 4 && w.Item2 == 4);
if (result == null)
Console.WriteLine("Not found");
else
Console.WriteLine("Found");
}
}
_
うまくいきました。私は新しい構文のより簡単に解釈される意図が好きですが、何が見つかったか(または見つからなかったか)を操作する前にそれをnullチェックする方法がわかりません。
値タプルは値タイプです。それらをnullにすることはできません。そのため、コンパイラは不平を言います。古いタプル型は参照型でした
この場合のFirstOrDefault()
の結果は、ValueTuple<int,int,int>
のデフォルトのインスタンスになります-すべてのフィールドはデフォルト値0に設定されます。
デフォルトを確認する場合は、結果をデフォルト値のValueTuple<int,int,int>
と比較できます。例:
var result=(new List<(int a, int b, int c)>()
{
(1, 1, 2),
(1, 2, 3),
(2, 2, 4)
}
).FirstOrDefault(w => w.a == 4 && w.b == 4);
if (result.Equals(default(ValueTuple<int,int,int>)))
{
Console.WriteLine("Missing!");
}
Word OF WARNING
このメソッドはFirstOrDefault
ではなくTryFirst
と呼ばれます。値が存在するかどうかを確認することを意図したものではありませんが、私たち全員がこのように(ab)使用しています。
C#でこのような拡張メソッドを作成することはそれほど難しくありません。古典的なオプションは、出力パラメーターを使用することです。
public static bool TryFirst<T>(this IEnumerable<T> seq,Func<T,bool> filter, out T result)
{
result=default(T);
foreach(var item in seq)
{
if (filter(item)) {
result=item;
return true;
}
}
return false;
}
これを呼び出すと、C#7では次のように簡略化できます。
if (myList.TryFirst(w => w.a == 4 && w.b == 1,out var result))
{
Console.WriteLine(result);
}
F#開発者は Seq.tryPick があり、一致が見つからない場合にNone
を返すことを自慢できます。
C#にはOption型や(まだ)たぶん型はありませんが、多分(意図的に)独自に構築できます。
class Option<T>
{
public T Value {get;private set;}
public bool HasValue {get;private set;}
public Option(T value) { Value=value; HasValue=true;}
public static readonly Option<T> Empty=new Option<T>();
private Option(){}
public void Deconstruct(out bool hasValue,out T value)
{
hasValue=HasValue;
value=Value;
}
}
public static Option<T> TryPick<T>(this IEnumerable<T> seq,Func<T,bool> filter)
{
foreach(var item in seq)
{
if (filter(item)) {
return new Option<T>(item);
}
}
return Option<T>.Empty;
}
これにより、次のGoスタイルの呼び出しを記述できます。
var (found,value) =myList.TryPick(w => w.a == 4 && w.b == 1);
より伝統的なことに加えて:
var result=myList.TryPick(w => w.a == 4 && w.b == 1);
if (result.HasValue) {...}
値の型とFirstOrDefault
を処理するためのもう1つの選択肢を追加するには、Where
を使用して、結果をnull許容型にキャストします。
var result = Map.Where(w => w.a == 4 && w.b == 4)
.Cast<(int a, int b, int c)?>().FirstOrDefault();
if (result == null)
Console.WriteLine("Not found");
else
Console.WriteLine("Found");
あなたはそれの拡張メソッドを作ることさえできます:
public static class Extensions {
public static T? StructFirstOrDefault<T>(this IEnumerable<T> items, Func<T, bool> predicate) where T : struct {
return items.Where(predicate).Cast<T?>().FirstOrDefault();
}
}
次に、元のコードがコンパイルされます(FirstOrDefault
をStructFirstOrDefault
に置き換えた場合)。
パナギオティスによって書かれたように、あなたはそれを直接行うことはできません...あなたは少し「騙す」ことができます:
_var result = Map.Where(w => w.a == 4 && w.b == 4).Take(1).ToArray();
if (result.Length == 0)
Console.WriteLine("Not found");
else
Console.WriteLine("Found");
_
Where
を使用して最大1つの要素を取得し、結果を長さ0〜1の配列に格納します。
または、比較を繰り返すこともできます。
_var result = Map.FirstOrDefault(w => w.a == 4 && w.b == 4);
if (result.a == 4 && result.b == 4)
Console.WriteLine("Not found");
_
あなたが探していた場合、この2番目のオプションは機能しません
_var result = Map.FirstOrDefault(w => w.a == 0 && w.b == 0);
_
この場合、FirstOrDefault()
has _a == 0
_および_b == 0
_によって返される「デフォルト」値。
または、_out bool success
_(さまざまなTryParse
のような)を持つ "特別な" FirstOrDefault()
を作成することもできます。
_static class EnumerableEx
{
public static T FirstOrDefault<T>(this IEnumerable<T> source, Func<T, bool> predicate, out bool success)
{
if (source == null)
{
throw new ArgumentNullException(nameof(source));
}
if (predicate == null)
{
throw new ArgumentNullException(nameof(predicate));
}
foreach (T ele in source)
{
if (predicate(ele))
{
success = true;
return ele;
}
}
success = false;
return default(T);
}
}
_
次のように使用してください:
_bool success;
var result = Map.FirstOrDefault(w => w.a == 4 && w.b == 4, out success);
_
その他の可能な拡張メソッド、ToNullable<>()
_static class EnumerableEx
{
public static IEnumerable<T?> ToNullable<T>(this IEnumerable<T> source) where T : struct
{
return source.Cast<T?>();
}
}
_
次のように使用します。
_var result = Map.Where(w => w.a == 4 && w.b == 4).ToNullable().FirstOrDefault();
if (result == null)
_
result
は_T?
_であるため、その値を使用するには_result.Value
_を実行する必要があることに注意してください。
データセットに(0, 0, 0)
が含まれないことが確実な場合は、他の人が言ったように、デフォルトを確認できます。
if (result.Equals(default(ValueTuple<int,int,int>))) ...
ただし、その値が発生する可能性がある場合は、First
を使用して、一致しない場合に例外をキャッチできます。
class Program
{
private static readonly List<(int a, int b, int c)> Map =
new List<(int a, int b, int c)>()
{
(1, 1, 2),
(1, 2, 3),
(2, 2, 4),
(0, 0, 0)
};
static void Main(string[] args)
{
try
{
Map.First(w => w.a == 0 && w.b == 0);
Console.WriteLine("Found");
}
catch (InvalidOperationException)
{
Console.WriteLine("Not found");
}
}
}
または、ライブラリ 自分のSuccinc <T>ライブラリなど を使用して、一致しない場合は「多分」タイプのTryFirst
、または一致する場合はアイテムを返すnone
メソッドを提供できます。
class Program
{
private static readonly List<(int a, int b, int c)> Map =
new List<(int a, int b, int c)>()
{
(1, 1, 2),
(1, 2, 3),
(2, 2, 4),
(0, 0, 0)
};
static void Main(string[] args)
{
var result = Map.TryFirst(w => w.a == 0 && w.b == 0);
Console.WriteLine(result.HasValue ? "Found" : "Not found");
}
}
チェックは次のようになります。
if (!Map.Any(w => w.a == 4 && w.b == 4))
{
Console.WriteLine("Not found");
}
else
{
var result = Map.First(w => w.a == 4 && w.b == 4);
Console.WriteLine("Found");
}
ValueTupleは、C#7タプルに使用される基本型です。これらは値型であるため、nullにすることはできません。デフォルトでテストすることもできますが、実際には有効な値である可能性があります。
また、等価演算子はValueTupleで定義されていないため、Equals(...)を使用する必要があります。
static void Main(string[] args)
{
var result = Map.FirstOrDefault(w => w.Item1 == 4 && w.Item2 == 4);
if (result.Equals(default(ValueTuple<int, int, int>)))
Console.WriteLine("Not found");
else
Console.WriteLine("Found");
}
必要なもの:
if (result.Equals(default)) Console.WriteLine(...
(c#> 7.1)
C#7.3では非常にクリーンです。
var result = Map.FirstOrDefault(w => w.a == 4 && w.b == 4);
if (result == default) {
Console.WriteLine("Not found");
} else {
Console.WriteLine("Found");
}
c#7.3でどのようにしたか
T findme;
var Tuple = list.Select((x, i) => (Item: x, Index: i)).FirstOrDefault(x => x.Item.GetHashCode() == findme.GetHashCode());
if (Tuple.Equals(default))
return;
...
var index = Tuple.Index;
上記の答えのほとんどは、結果の要素をdefault(T)にすることはできないことを示しています。Tはクラス/タプルです。
これを回避する簡単な方法は、次のようなアプローチを使用することです。
var result = Map
.Select(t => (t, IsResult:true))
.FirstOrDefault(w => w.t.Item1 == 4 && w.t.Item2 == 4);
Console.WriteLine(result.IsResult ? "Found" : "Not found");
このサンプルでは、C#7.1の暗黙のタプル名(およびC#7のValueTupleパッケージ)を使用していますが、必要に応じて明示的にタプル要素に名前を付けるか、代わりに単純なTuple<T1,T2>
を使用できます。