簡體   English   中英

如何在 LINQ 查詢中空檢查 c# 7 元組?

[英]How to null check c# 7 tuple in LINQ query?

鑒於:

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)”和“<null>”類型的操作數

在繼續“找到”邏輯之前,我將如何檢查是否找到了元組?

在使用新的 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");
    }
}

哪個工作得很好。 我喜歡新語法更容易解釋的意圖,但我不確定如何在對發現的(或沒有)采取行動之前對其進行空檢查。

值元組是值類型。 它們不能為空,這就是編譯器抱怨的原因。 舊的元組類型是引用類型

在這種情況下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!"); 
}

警告語

該方法稱為FirstOrDefault ,而不是TryFirst 這並不意味着檢查一個值是否存在,盡管我們都(ab)以這種方式使用它。

在 C# 中創建這樣的擴展方法並不困難。 經典選項是使用 out 參數:

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 類型或 Maybe 類型(還),但也許(雙關語)我們可以構建自己的:

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 :使用Where並將結果轉換為可空類型:

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 )。

正如 Panagiotis 所寫,你不能直接做到這一點......你可以“作弊”一點:

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獲取一個元素,並將結果放入長度為 0-1 的數組中。

或者,您可以重復比較:

var result = Map.FirstOrDefault(w => w.a == 4 && w.b == 4);

if (result.a == 4 && result.b == 4)
    Console.WriteLine("Not found");

如果您正在尋找,則第二個選項將不起作用

var result = Map.FirstOrDefault(w => w.a == 0 && w.b == 0);

在這種情況下, FirstOrDefault()返回的“默認”值具有a == 0b == 0

或者你可以簡單地創建一個“特殊的” FirstOrDefault() ,它有一個out bool success (如各種TryParse ):

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 元組的基礎類型。 它們不能為空,因為它們是值類型。 您可以測試它們的默認值,但這實際上可能是一個有效值。

此外,相等運算符未在 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>代替。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM