LINQ-запрос для выполнения исключений проекции, пропуска или обтекания, при которых источник генерирует IEnumerable.GetNext () - PullRequest
21 голосов
/ 25 августа 2011

Я бы хотел общее решение , но в качестве примера предположим, что у меня есть 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().

Ответы [ 6 ]

17 голосов
/ 25 августа 2011

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

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 Добавлен оператор использования, спасибо за предложение в комментариях

    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 еще не был создан (и, следовательно, уже выдал исключения) в виде списка предыдущей функцией. Например. это, вероятно, не будет работать, если вы вызовете его следующим образом: Select (..). ToList (). SkipExceptions ()

7 голосов
/ 25 августа 2011

Создайте метод 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;
    }
}
2 голосов
/ 10 февраля 2014

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

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

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
  • Записать исключение и т. Д.
1 голос
/ 25 августа 2011

Вот небольшая полная программа, чтобы продемонстрировать ответ, вдохновленный, возможно, монадой.Возможно, вы захотите изменить имя класса «Maybe», так как оно вдохновлено, а не фактически «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) равен нулю.

0 голосов
/ 25 августа 2011

Это тот же ответ, что и у Томаса, но с лямбда-выражением и 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;
0 голосов
/ 25 августа 2011

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

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

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

...