如何在 LINQ 查询中检查 c # 7元组为空?

给出:

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

效果不错。我喜欢新语法更容易理解的意图,但是不确定如何在执行发现(或不执行)之前检查它是否为 null。

30548 次浏览

值元组是值类型。它们不能为空,这就是编译器抱怨的原因。旧的 Tuple 类型是引用类型

在这种情况下,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 # 开发人员可以吹嘘他们有一个 继续,试试看,如果没有找到匹配,它将返回 None

C # 没有选项类型或可能类型(还没有) ,但也许(双关语)我们可以构建自己的:

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) {...}

就像 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返回“ default”值。

或者您可以简单地创建一个具有 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)

请注意,resultT?,因此需要执行 result.Value才能使用它的值。

您的支票可以是:

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

只是添加一个处理值类型和 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();
}
}

然后将编译原始代码(假设用 StructFirstOrDefault替换 FirstOrDefault)。

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

如果你确定你的数据集不包括 (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");
}
}

上面的大多数答案都暗示结果元素不能是默认的(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隐含的 tuple 名称(C # 7使用 ValueTuple 包) ,但是如果需要,可以显式地为 tuple 元素提供名称,或者使用简单的 Tuple<T1,T2>

你需要:

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

(c # > 7.1)

我是怎么用 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;

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