web-dev-qa-db-ja.com

LINQクエリでC#7タプルをnullチェックする方法は?

与えられた:

_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チェックする方法がわかりません。

47
Kritner

値タプルは値タイプです。それらを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) {...}
53

値の型と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();
    }
}

次に、元のコードがコンパイルされます(FirstOrDefaultStructFirstOrDefaultに置き換えた場合)。

16
Evk

パナギオティスによって書かれたように、あなたはそれを直接行うことはできません...あなたは少し「騙す」ことができます:

_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_を実行する必要があることに注意してください。

6
xanatos

データセットに(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");
    }
}
6
David Arno

チェックは次のようになります。

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");
}
5
Kevin Sijbers

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");
}
4

必要なもの:

if (result.Equals(default)) Console.WriteLine(...

(c#> 7.1)

2
kofifus

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");
}
0
David

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;
0
tom nobleman

上記の答えのほとんどは、結果の要素を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>を使用できます。

0
Jay Haybatov