Выберите синтаксический анализ 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: