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

Запрос LINQ для выполнения проецирования, пропусков или обертывания исключений, где источник генерирует IEnumerable.GetNext()

Я бы хотел общее решение, но в качестве примера предположим, что у меня есть IEnumerable<string>, где некоторые могут быть проанализированы как целые числа, а некоторые не могут.

var strings = new string[] { "1", "2", "notint", "3" };

Очевидно, если бы я сделал Select(s => int.Parse(s, temp)), он перебрасывал бы исключение при перечислении.

В этом случае я мог бы сделать .All(s => int.TryParse(s, out temp)) во-первых, однако мне нужно общее решение, в котором я не должен дважды перечислять IEnumerable.

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

// e.g. parsing strings
var strings = new string[] { "1", "2", "notint", "3" };
var numbers = strings.Select(s => int.Parse(s)).SkipExceptions();
// e.g. encountering null object
var objects = new object[] { new object(), new object(), null, new object() }
var objecttostrings = objects.Select(o => o.ToString()).SkipExceptions();
// e.g. calling a method that could throw
var myClassInstances = new MyClass[] { new MyClass(), new MyClass(CauseMethodToThrow:true) };
var myClassResultOfMethod = myClassInstances.Select(mci => mci.MethodThatCouldThrow()).SkipExceptions();

Как я могу написать метод расширения SkipExceptions()?


Некоторые отличные ответы для метода SelectSkipExceptions(), однако мне интересно, может ли быть создан метод SkipExceptions() в тех же строках, что и AsParallel().

4b9b3361

Ответ 1

Как насчет этого (возможно, вы захотите присвоить этому специальному Select Extension лучшее имя)

public static IEnumerable<TOutput> SelectIgnoringExceptions<TInput, TOutput>(
    this IEnumerable<TInput> values, Func<TInput, TOutput> selector)
   {
        foreach (var item in values)
        {
            TOutput output = default(TOutput);

            try
            {
                output = selector(item);
            }
            catch 
            {
                continue;
            }

            yield return output;
        }
    }

Edit5 Добавлен оператор using, спасибо за предложение в комментариях

    public static IEnumerable<T> SkipExceptions<T>(
        this IEnumerable<T> values)
    {
        using(var enumerator = values.GetEnumerator())
        {
           bool next = true;
           while (next)
           {
               try
               {
                   next = enumerator.MoveNext();
               }
               catch
               {
                   continue;
               }

               if(next) yield return enumerator.Current;
           } 
        }
    }

Однако это зависит от входящего IEnumerable, который уже не создается (и, следовательно, уже выбрал Exceptions), как список предыдущей функции. Например. это, вероятно, будет не, если вы вызываете это следующим образом: Выберите (..). ToList(). SkipExceptions()

Ответ 2

Создайте метод TryParseInt, который возвращает Nullable<int>:

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

И используйте его в своем запросе следующим образом:

var numbers = strings.Select(s => TryParseInt(s))
                     .Where(i => i.HasValue)
                     .Select(i => i.Value);

См. также эту статью Билла Вагнера, который представляет очень похожий случай.


Теперь я не думаю, что вы можете написать что-то вроде универсального метода SkipExceptions, потому что вы слишком поздно поймаете исключение и закончите цикл Select... Но вы, вероятно, могли бы написать SelectSkipException:

public static IEnumerable<TResult> SelectSkipExceptions<TSource, TResult>(
    this IEnumerable<TSource> source,
    Func<TSource, TResult> selector)
{
    if (source == null)
        throw new ArgumentNullException("source");
    if (selector == null)
        throw new ArgumentNullException("selector");
    return source.SelectSkipExceptionsIterator(selector);
}

private static IEnumerable<TResult> SelectSkipExceptionsIterator<TSource, TResult>(
    this IEnumerable<TSource> source,
    Func<TSource, TResult> selector)
{
    foreach(var item in source)
    {
        TResult value = default(TResult);
        try
        {
            value = selector(item);
        }
        catch
        {
            continue;
        }
        yield return value;
    }
}

Ответ 3

Даже принятый ответ не может быть "общим" достаточно. Что, если в какой-то день вы обнаружите, что вам нужно знать, какие исключения произошли?

Следующее расширение

static class EnumeratorHelper {

    //Don't forget that GetEnumerator() call can throw exceptions as well.
    //Since it is not easy to wrap this within a using + try catch block with yield,
    //I have to create a helper function for the using block.
    private static IEnumerable<T> RunEnumerator<T>(Func<IEnumerator<T>> generator, 
        Func<Exception, bool> onException)
    {
        using (var enumerator = generator())
        {
            if (enumerator == null) 
                yield break;
            for (; ; )
            {
                //You don't know how to create a value of T,
                //and you don't know weather it can be null,
                //but you can always have a T[] with null value.
                T[] value = null;
                try
                {
                    if (enumerator.MoveNext())
                        value = new T[] { enumerator.Current };
                }
                catch (Exception e)
                {
                    if (onException(e))
                        continue;
                }
                if (value != null)
                    yield return value[0];
                else
                    yield break;
            }
        }
    }

    public static IEnumerable<T> WithExceptionHandler<T>(this IEnumerable<T> orig, 
        Func<Exception, bool> onException)
    {
        return RunEnumerator(() =>
        {
            try
            {
                return orig.GetEnumerator();
            }
            catch (Exception e)
            {
                onException(e);
                return null;
            }
        }, onException);
    }

}

поможет. Теперь вы можете добавить SkipExceptions:

 public static IEnumerable<T> SkipExceptions<T>(this IEnumerable<T> orig){
     return orig.WithExceptionHandler(orig, e => true);
 }

Используя другой обратный вызов onException, вы можете делать разные вещи

  • Разбить итерацию, но игнорировать исключение: e => false
  • Попробуйте продолжить итерацию: e => true
  • Записать исключение и т.д.

Ответ 4

Здесь небольшая полная программа, чтобы продемонстрировать ответ, вдохновленный, возможно, монадой. Возможно, вы захотите изменить имя класса "Maybe", поскольку оно вдохновлено, а не фактически "возможно", как определено на других языках.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace TestMaybe
{
    class Program
    {
        static void Main(string[] args)
        {
            var strings = new string[] { "1", "2", "notint", "3" };
            var ints = strings.Select(s => new Maybe<string, int>(s, str => int.Parse(str))).Where(m => !m.nothing).Select(m => m.value);
            foreach (var i in ints)
            {
                Console.WriteLine(i);
            }
            Console.ReadLine();

        }
    }

    public class Maybe<T1, T2>
    {
        public readonly bool nothing;
        public readonly T2 value;

        public Maybe(T1 input, Func<T1, T2> map)
        {
            try
            {
                value = map(input);
            }
            catch (Exception)
            {
                nothing = true;
            }            
        }
    }
}

Изменить: в зависимости от потребностей вашего кода вы также можете захотеть nothing установить true, если результат map(input) равен нулю.

Ответ 5

Это тот же самый ответ, что и у Томаса, но с выражением лямбда и LINQ. +1 для Томаса.

Func<string, int?> tryParse = s =>
{
    int? r = null;
    int i;
    if (int.TryParse(s, out i))
    {
        r = i;
    }
    return r;
};

var ints =
    from s in strings
    let i = tryParse(s)
    where i != null
    select i.Value;

Ответ 6

Вы можете просто объединить метод Where и Select.

var numbers = strings.Where(s =>
                      {
                          int i;
                          return int.TryParse(s, out i);
                      }).Select(int.Parse);

Использование метода Where эффективно устраняет необходимость написания собственного метода SkipExceptions, потому что это в основном то, что вы делаете.