Выберите синтаксический анализ int, если строка была подвержена анализу int

Итак, у меня есть IEnumerable<string>, который может содержать значения, которые могут быть проанализированы как int, а также значения, которые не могут быть.

Как вы знаете, Int32.Parse генерирует исключение, если строка не может быть изменена на int, а Int32.TryParse может использоваться для проверки и проверки того, возможно ли преобразование без обращения к исключению.

Итак, я хочу использовать LINQ-запрос для однострочного анализа, чтобы проанализировать те строки, которые могут быть проанализированы как int, но не выбрасывая исключение на этом пути. У меня есть решение, но я хотел бы получить совет от сообщества о том, является ли это наилучшим подходом.

Вот что у меня есть:

int asInt = 0;
var ints = from str in strings
           where Int32.TryParse(str, out asInt)
           select Int32.Parse(str);

Итак, как вы можете видеть, я использую asInt в качестве пространства скреста для вызова TryParse, чтобы определить, будет ли TryParse успешным (return bool). Затем, в проекции, я фактически выполняю синтаксический анализ. Это кажется уродливым.

Это лучший способ фильтрации анализируемых значений в одной строке с помощью LINQ?

Ответ 1

Это трудно сделать в синтаксисе запроса, но это не так уж плохо в синтаксисе лямбда:

var ints = strings.Select(str => {
                             int value;
                             bool success = int.TryParse(str, out value);
                             return new { value, success };
                         })
                  .Where(pair => pair.success)
                  .Select(pair => pair.value);

В качестве альтернативы вам может потребоваться написать метод, который возвращает int?:

public static int? NullableTryParseInt32(string text)
{
    int value;
    return int.TryParse(text, out value) ? (int?) value : null;
}

Тогда вы можете просто использовать:

var ints = from str in strings
           let nullable = NullableTryParseInt32(str)
           where nullable != null
           select nullable.Value;

Ответ 2

Это еще два кода, но вы можете немного сократить свой оригинал:

int asInt = 0;
var ints = from str in strings
           where Int32.TryParse(str, out asInt)
           select asInt;

Так как TryParse уже выполняется во время выбора, переменная asInt заполняется, поэтому вы можете использовать ее как возвращаемое значение - вам не нужно снова ее анализировать.

Ответ 3

Если вы не возражаете против того, чтобы ваши коллеги прыгали на стоянке, есть способ сделать это в одной истинной строке linq (точки с запятой)....

strings.Select<string, Func<int,int?>>(s => (n) => int.TryParse(s, out n) ? (int?)n : (int?)null ).Where(λ => λ(0) != null).Select(λ => λ(0).Value);

Это не практично, но делать это в одном из заявлений было слишком интересным, чтобы пройти мимо.

Ответ 4

У меня, вероятно, был бы этот небольшой метод утилиты где-нибудь (я действительно делаю в своей текущей кодовой базе:-))

public static class SafeConvert
{
    public static int? ToInt32(string value) 
    {
        int n;
        if (!Int32.TryParse(value, out n))
            return null;
        return n;
    }
}

Затем вы используете этот более простой оператор LINQ:

from str in strings
let number = SafeConvert.ToInt32(str)
where number != null
select number.Value;

Ответ 5

Я бы это LINQ-to-objects:

static int? ParseInt32(string s) {
    int i;
    if(int.TryParse(s,out i)) return i;
    return null;
}

Затем в запросе:

let i = ParseInt32(str)
where i != null
select i.Value;

Ответ 6

Если вы хотите определить метод расширения для этого, я бы создал общее простое в использовании решение, вместо того чтобы потребовать, чтобы вы написали новую обертку с нулевым именем для каждой функции Try, и требуется отфильтровать нулевые значения.

public delegate bool TryFunc<in TSource, TResult>(TSource arg, out TResult result);

public static IEnumerable<TResult> SelectTry<TSource, TResult>(this IEnumerable<TSource> source, TryFunc<TSource, TResult> selector)
{
    foreach(var s in source) {
        TResult r;
        if (selector(s, out r))
            yield return r;
    }
}

Использование:

var ints = strings.SelectTry<string, int>(int.TryParse);

Немного неудобно, что С# не может вывести аргументы типа SelectTry общего типа.

(TryFunc TResult не может быть ковариантным (т.е. out TResult), например Func. Как Эрик Липперт объясняет параметры на самом деле просто ref параметры с фантастическими правилами написания перед чтением.)

Ответ 7

Вдохновленный ответом Карла Уолша, я сделал еще один шаг, чтобы разрешить синтаксический анализ свойств:

public static IEnumerable<TResult> SelectTry<TSource, TValue, TResult>(
     this IEnumerable<TSource> source, 
     Func<TSource, TValue> selector, 
     TryFunc<TValue, TResult> executor)
{
    foreach (TSource s in source)
    {
        TResult r;
        if (executor(selector(s), out r))
            yield return r;
    }
}

Вот пример, который также можно найти в этом fiddle:

public class Program
{
    public static void Main()
    {       
        IEnumerable<MyClass> myClassItems = new List<MyClass>() {new MyClass("1"), new MyClass("2"), new MyClass("InvalidValue"), new MyClass("3")};

        foreach (int integer in myClassItems.SelectTry<MyClass, string, int>(x => x.MyIntegerAsString, int.TryParse))
        {
            Console.WriteLine(integer);
        }
    }
}

public static class LinqUtilities
{
    public delegate bool TryFunc<in TSource, TResult>(TSource arg, out TResult result);

    public static IEnumerable<TResult> SelectTry<TSource, TValue, TResult>(
        this IEnumerable<TSource> source, 
        Func<TSource, TValue> selector, 
        TryFunc<TValue, TResult> executor)
    {
        foreach (TSource s in source)
        {
            TResult r;
            if (executor(selector(s), out r))
                yield return r;
        }
    }
}

public class MyClass
{
    public MyClass(string integerAsString)
    {
        this.MyIntegerAsString = integerAsString;
    }

     public string MyIntegerAsString{get;set;}
}

Вывод этой программы:

1

2

3

Ответ 8

Если вы ищете однострочное выражение Linq и прекрасно выделяете новый объект в каждом цикле, я бы использовал более мощный SelectMany, чтобы сделать это с помощью одного вызова Linq

var ints = strings.SelectMany(str => {
    int value;
    if (int.TryParse(str, out value))
        return new int[] { value };
    return new int[] { };
});

Ответ 9

Я согласен, что использование дополнительной переменной кажется уродливым.

На основе Jon answer и обновление до решений С# 7.0 можно использовать новый var out функция: (не намного короче, но не требуется для внутренней области или из временных переменных запроса)

var result = strings.Select(s => new { Success = int.TryParse(s, out var value), value })
                    .Where(pair => pair.Success)
                    .Select(pair => pair.value);

и вместе с именованными кортежами:

var result = strings.Select(s => (int.TryParse(s, out var value), value))
                    .Where(pair => pair.Item1)
                    .Select(pair => pair.value);

Или если вы предлагаете метод для использования в синтаксисе запроса:

public static int? NullableTryParseInt32(string text)
{
    return int.TryParse(text, out var value) ? (int?)value : null;
}

Я также хотел бы предложить синтаксис запроса без дополнительного метода для него, но как обсуждалось в следующей ссылке out var не поддерживается С# 7.0 и приводит к ошибке компиляции:

Объявления переменной переменной и шаблона не допускаются в предложении запроса

Ссылка: Выражение переменных в выражениях запроса


С помощью этой функции С# 7.0 можно заставить ее работать с более ранними версиями .NET: