[英]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 == 0
和b == 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.