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

Kri*_*ner 41 c# linq c#-7.0

鉴于:

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");
    }
}
Run Code Online (Sandbox Code Playgroud)

在上面的示例中,遇到编译器错误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");
    }
}
Run Code Online (Sandbox Code Playgroud)

哪个工作正常.我喜欢新语法更容易解释的意图,但我不确定如何在对发现的内容(或不发现)进行操作之前对其进行null检查.

Pan*_*vos 50

值元组是值类型.它们不能为null,这就是编译器抱怨的原因.旧的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!"); 
}
Run Code Online (Sandbox Code Playgroud)

警告的词

该方法被调用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;
}
Run Code Online (Sandbox Code Playgroud)

在C#7中可以简化调用此操作:

if (myList.TryFirst(w => w.a == 4 && w.b == 1,out var result))
{
    Console.WriteLine(result);
}
Run Code Online (Sandbox Code Playgroud)

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;
}
Run Code Online (Sandbox Code Playgroud)

这允许编写以下Go样式调用:

var (found,value) =myList.TryPick(w => w.a == 4 && w.b == 1);
Run Code Online (Sandbox Code Playgroud)

除了更传统的:

var result=myList.TryPick(w => w.a == 4 && w.b == 1);
if (result.HasValue) {...}
Run Code Online (Sandbox Code Playgroud)

  • 在C#7.1中,您现在可以编写:`if(result.Equals(default))`,它将自动推断`default`的类型。要使用C#7.1,您需要使用Visual Studio 2017的最新版本,并且还需要在项目生成设置(``高级...'')中将C#版本设置为7.1。 (3认同)
  • `Option` 非常适合作为 `struct`,类似于 `Nullable`。否则,您需要处理可选项的可为空性,这会令人困惑。 (2认同)

Des*_*ond 17

在 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");
}
Run Code Online (Sandbox Code Playgroud)


Evk*_*Evk 15

只是添加一个替代方法来处理值类型和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");
Run Code Online (Sandbox Code Playgroud)

你甚至可以制作它的扩展方法:

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();
    }
}
Run Code Online (Sandbox Code Playgroud)

然后你原来的代码将编译(假设您更换FirstOrDefaultStructFirstOrDefault).

  • 或者可能只是将其称为“FirstOrNull”,因为它的目的是在未找到任何内容时返回空值。+1 (2认同)

xan*_*tos 6

由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");
Run Code Online (Sandbox Code Playgroud)

最多使用一个元素,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");
Run Code Online (Sandbox Code Playgroud)

如果您正在寻找,第二个选项将无效

var result = Map.FirstOrDefault(w => w.a == 0 && w.b == 0);
Run Code Online (Sandbox Code Playgroud)

在这种情况下,FirstOrDefault() has 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);
    }
}
Run Code Online (Sandbox Code Playgroud)

使用它像:

bool success;
var result = Map.FirstOrDefault(w => w.a == 4 && w.b == 4, out success);
Run Code Online (Sandbox Code Playgroud)

其他可能的扩展方法, ToNullable<>()

static class EnumerableEx
{
    public static IEnumerable<T?> ToNullable<T>(this IEnumerable<T> source) where T : struct
    {
        return source.Cast<T?>();
    }
}
Run Code Online (Sandbox Code Playgroud)

使用它像:

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

if (result == null)
Run Code Online (Sandbox Code Playgroud)

请注意,这result是一个T?,因此您需要result.Value使用它的值.


Dav*_*rno 6

如果您确定您的数据集不包含(0, 0, 0),那么正如其他人所说,您可以检查默认值:

if (result.Equals(default(ValueTuple<int,int,int>))) ...
Run Code Online (Sandbox Code Playgroud)

如果该值可能会出现,那么您可以使用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");
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

或者,你可以使用一个库,比如我自己的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");
    }
}
Run Code Online (Sandbox Code Playgroud)


kof*_*fus 6

你需要:

if (result.Equals(default)) Console.WriteLine(...
Run Code Online (Sandbox Code Playgroud)

(c# > 7.1)


Kev*_*ers 5

您的支票可能如下:

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");
}
Run Code Online (Sandbox Code Playgroud)