Выберите parsed int, если строка была разбирается в int - PullRequest
47 голосов
/ 10 февраля 2011

Итак, у меня есть 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 успешным (возврат bool).Затем в проекции я фактически выполняю разбор.Это выглядит ужасно.

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

Ответы [ 9 ]

78 голосов
/ 10 февраля 2011

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

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;
13 голосов
/ 10 февраля 2011

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

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

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

6 голосов
/ 11 февраля 2011

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

4 голосов
/ 10 февраля 2011

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

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;
2 голосов
/ 19 декабря 2014

Если вы хотите определить метод расширения, чтобы сделать это, я бы создал простое в использовании общее решение, вместо того, чтобы требовать, чтобы вы писали новую оболочку с нулевым значением при ошибке для каждой функции 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);

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

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

2 голосов
/ 10 февраля 2011

Я бы это 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;
1 голос
/ 29 марта 2017

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

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 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

0 голосов
/ 11 сентября 2017

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

На основании ответа Джона и обновления до решений C # 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 не поддерживается c # 7.0 и приводит к ошибке компиляции:

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

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


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

0 голосов
/ 19 декабря 2014

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

var ints = strings.SelectMany(str => {
    int value;
    if (int.TryParse(str, out value))
        return new int[] { value };
    return new int[] { };
});
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...