sintaxis ejemplos consultas c# linq exception-handling ienumerable wrapping

c# - ejemplos - Consulta LINQ para realizar una proyección, omitiendo o envolviendo excepciones donde la fuente se lanza en IEnumerable.GetNext()



consultas linq c# ejemplos (6)

¿Qué tal esto? (Es posible que desee darle un mejor nombre a esta extensión especial de selección)

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 Agregado una declaración usando, gracias por la sugerencia en los comentarios

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

Sin embargo, esto se basa en que el IEnumerable entrante no se haya creado (y, por lo tanto, ya haya lanzado Excepciones) como una lista de la Función anterior. Por ejemplo, es probable que esto no funcione si lo llama así: Seleccione (..). ToList (). SkipExceptions ()

Me gustaría una solución general, pero como ejemplo, asumo que tengo una IEnumerable<string> , donde algunos pueden analizarse como enteros y otros no.

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

Obviamente, si Select(s => int.Parse(s, temp)) lanzaría una excepción cuando se enumera.

En este caso, podría hacer .All(s => int.TryParse(s, out temp)) , sin embargo, quiero una solución general donde no tenga que enumerar IEnumerable dos veces.

Lo ideal sería poder hacer lo siguiente, que llama a mi método de omisión de excepción mágica:

// 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();

¿Cómo puedo escribir el método de extensión SkipExceptions() ?

Algunas respuestas geniales para un método SelectSkipExceptions() , sin embargo, me pregunto si podría crearse un método SkipExceptions() , en la misma línea que AsParallel() .


Aquí hay un pequeño programa completo para demostrar una respuesta inspirada en la mónada tal vez. Es posible que desee cambiar el nombre de la clase ''Tal vez'', ya que está inspirado en lugar de ser un ''Tal vez'' tal como se define en otros idiomas.

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

Editar: dependiendo de las necesidades de su código, es posible que tampoco desee establecer nothing como true si el resultado del map(input) es nulo.


Cree un método TryParseInt que devuelva un Nullable<int> :

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

Y utilízalo en tu consulta así:

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

Vea también este artículo de Bill Wagner, que presenta un caso muy similar.

Ahora, no creo que pueda escribir algo como un método genérico de SkipExceptions , ya que detectaría la excepción demasiado tarde, y finalizaría el bucle Select ... Pero probablemente podría escribir un método 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; } }


Esta es la misma respuesta que la de Thomas, pero con una expresión lambda y LINQ. +1 para Thomas.

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;


Incluso la respuesta aceptada puede no ser lo suficientemente "general". ¿Qué sucede si algún día descubre que necesita saber qué excepciones se produjeron?

La siguiente extensión

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); } }

ayudará. Ahora puedes agregar SkipExceptions :

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

Al utilizar la devolución de llamada onException diferente, puede hacer cosas diferentes

  • Romper la iteración pero ignorar la excepción: e => false
  • Intenta continuar la iteración: e => true
  • Registrar la excepción, etc.

Usted podría simplemente encadenar el método Where y Select juntos.

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

El uso del método Where elimina efectivamente la necesidad de que escriba su propio método SkipExceptions, porque esto es básicamente lo que está haciendo.