Подтвердить что ты не робот

Как проверить нулевой набор кортежей С# 7 в запросе LINQ?

Дано:

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)' и ''

Как я могу проверить, что кортеж найден до начала моей "найденной" логики?

До использования новых кортежей С# 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");
    }
}

Это сработало нормально. Мне нравится более легко интерпретируемое намерение нового синтаксиса, но я не уверен, как его проверить, прежде чем действовать на то, что было найдено (или нет).

4b9b3361

Ответ 1

Корзины значений - это типы значений. Они не могут быть пустыми, поэтому компилятор жалуется. Старый тип 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) используем его таким образом.

Создание такого метода расширения в С# не так уж сложно. Классическим вариантом является использование параметра 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;
}

Вызов этого можно упростить в С# 7 как:

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

Разработчики F # могут похвастаться тем, что у них есть Seq.tryPick, который вернет None, если совпадение не найдено.

У С# нет типов опций или типа 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) {...}

Ответ 2

Просто добавьте еще одну альтернативу для использования типов значений и FirstOrDefault: используйте 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();
    }
}

Затем ваш исходный код будет скомпилирован (если вы замените FirstOrDefault на StructFirstOrDefault).

Ответ 3

Как написано 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, чтобы использовать его значение.

Ответ 4

Если вы уверены, что ваш набор данных не будет включать (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");
        }
    }
}

В качестве альтернативы вы можете использовать библиотеку 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");
    }
}

Ответ 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");
}

Ответ 6

ValueTuple - это базовый тип, используемый для кортежей С# 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");
}

Ответ 7

Вам нужно:

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

(c #> 7.1)

Ответ 8

Большинство ответов выше подразумевают, что ваш результирующий элемент не может быть значением по умолчанию (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");

В этом примере используются подразумеваемые имена кортежей С# 7.1 (и пакет ValueTuple для С# 7), но при необходимости вы можете явно дать имя своим элементам кортежа или использовать вместо него простой Tuple<T1,T2>.

Ответ 9

как я это сделал с С# 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;

Ответ 10

В С# 7.3 это очень чисто:

var result = Map.FirstOrDefault(w => w.a == 4 && w.b == 4);
if (result == default) {
    Console.WriteLine("Not found");
} else {
    Console.WriteLine("Found");
}