Pregunta

Tengo la siguiente función para obtener los errores de validación de una tarjeta de crédito.Mi pregunta se refiere a tratar con GetErrors.Ambos métodos tienen el mismo tipo de retorno IEnumerable<ErrorInfo>.

private static IEnumerable<ErrorInfo> GetErrors(Card card)
{
    var errors = GetMoreErrors(card);
    foreach (var e in errors)
        yield return e;

    // further yield returns for more validation errors
}

Es posible volver a todos los errores en GetMoreErrors sin tener que enumerar a través de ellos?

Pensando que probablemente es una pregunta estúpida, pero quiero asegurarme de que no voy mal.

¿Fue útil?

Solución

Definitivamente no es una pregunta estúpida, y es algo que F# compatible con yield! para toda una colección de vs yield de un solo elemento.(Que puede ser muy útil en términos de la cola de la recursividad...)

Desafortunadamente no es compatible en C#.

Sin embargo, si usted tiene varios de los métodos que cada uno de devolver un IEnumerable<ErrorInfo>, usted puede utilizar Enumerable.Concat para hacer su código más simple:

private static IEnumerable<ErrorInfo> GetErrors(Card card)
{
    return GetMoreErrors(card).Concat(GetOtherErrors())
                              .Concat(GetValidationErrors())
                              .Concat(AnyMoreErrors())
                              .Concat(ICantBelieveHowManyErrorsYouHave());
}

Hay una diferencia muy importante entre las dos implementaciones, aunque:esto va a llamar a todos los métodos de inmediatamente, aunque sólo se hará uso de la devolución de los iteradores de una en una.Su código existente esperará hasta que el bucle a través de todo lo que en GetMoreErrors() incluso antes de que se pide acerca de la próxima errores.

Generalmente esto no es importante, pero vale la pena comprender lo que va a pasar cuando.

Otros consejos

Se puede configurar todas las fuentes de error como este (nombres de los métodos tomados de la respuesta de Jon Skeet).

private static IEnumerable<IEnumerable<ErrorInfo>> GetErrorSources(Card card)
{
    yield return GetMoreErrors(card);
    yield return GetOtherErrors();
    yield return GetValidationErrors();
    yield return AnyMoreErrors();
    yield return ICantBelieveHowManyErrorsYouHave();
}

A continuación, puede iterar sobre ellos al mismo tiempo.

private static IEnumerable<ErrorInfo> GetErrors(Card card)
{
    foreach (var errorSource in GetErrorSources(card))
        foreach (var error in errorSource)
            yield return error;
}

Si lo prefiere puede aplanar las fuentes de error con SelectMany.

private static IEnumerable<ErrorInfo> GetErrors(Card card)
{
    return GetErrorSources(card).SelectMany(e => e);
}

La ejecución de los métodos en GetErrorSources se retrasará también.

Se me ocurrió un fragmento yield_ rápida:

yield_ animación uso snipped

Aquí está el fragmento XML:

<?xml version="1.0" encoding="utf-8"?>
<CodeSnippets xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet">
  <CodeSnippet Format="1.0.0">
    <Header>
      <Author>John Gietzen</Author>
      <Description>yield! expansion for C#</Description>
      <Shortcut>yield_</Shortcut>
      <Title>Yield All</Title>
      <SnippetTypes>
        <SnippetType>Expansion</SnippetType>
      </SnippetTypes>
    </Header>
    <Snippet>
      <Declarations>
        <Literal Editable="true">
          <Default>items</Default>
          <ID>items</ID>
        </Literal>
        <Literal Editable="true">
          <Default>i</Default>
          <ID>i</ID>
        </Literal>
      </Declarations>
      <Code Language="CSharp"><![CDATA[foreach (var $i$ in $items$) yield return $i$$end$;]]></Code>
    </Snippet>
  </CodeSnippet>
</CodeSnippets>

No veo nada malo con su función, yo diría que está haciendo lo que quiere.

Piense en el rendimiento como devolver un elemento de la enumeración definitiva cada vez que se invoca, por lo que cuando lo tienes en el bucle foreach así, cada vez que se invoca que devuelve 1 elemento. Usted tiene la capacidad de poner las sentencias condicionales en su foreach para filtrar el conjunto de resultados. (Simplemente por no ceder en sus criterios de exclusión)

Si se agrega rendimientos posteriores más adelante en el método, que continuará añadir al menos 1 elemento de la enumeración, por lo que es posible hacer cosas como ...

public IEnumerable<string> ConcatLists(params IEnumerable<string>[] lists)
{
  foreach (IEnumerable<string> list in lists)
  {
    foreach (string s in list)
    {
      yield return s;
    }
  }
}

Sí, es posible devolver todos los errores a la vez. Simplemente envíe un List<T> o ReadOnlyCollection<T>.

Al devolver un IEnumerable<T> usted está volviendo una secuencia de algo. En la superficie que pueden parecer idénticos a devolver la colección, pero hay una serie de diferencias, se debe tener en cuenta.

Colecciones

  • La persona que llama puede estar seguro de que existirán tanto la recogida y todos los elementos cuando se devuelve la colección. Si la colección debe ser creado por llamada, el retorno de una colección es una muy mala idea.
  • La mayoría de las colecciones puede ser modificada cuando sea devuelto.
  • La colección es de tamaño finito.

Secuencias

  • se pueden enumerar -. Y que es más o menos todo lo que podemos decir con seguridad
  • A propia secuencia devuelto no puede ser modificado.
  • Cada elemento puede ser creado como parte de funcionamiento a través de la secuencia (es decir, volver IEnumerable<T> permite la evaluación perezosa, volviendo List<T> no lo hace).
  • Una secuencia puede ser infinita y por lo tanto dejar en manos de la persona que llama para decidir cuántos elementos debe ser devuelto.

Me sorprende que nadie ha pensado para recomendar un método simple extensión de IEnumerable<IEnumerable<T>> para hacer de este código a mantener su ejecución diferida. Soy un fan de ejecución diferida por muchas razones, una de ellas es que el consumo de memoria es pequeña incluso para grandes enumerables-mongous.

public static class EnumearbleExtensions
{
    public static IEnumerable<T> UnWrap<T>(this IEnumerable<IEnumerable<T>> list)
    {
        foreach(var innerList in list)
        {
            foreach(T item in innerList)
            {
                yield return item;
            }
        }
    }
}

Y se podría utilizar en su caso como éste

private static IEnumerable<ErrorInfo> GetErrors(Card card)
{
    return DoGetErrors(card).UnWrap();
}

private static IEnumerable<IEnumerable<ErrorInfo>> DoGetErrors(Card card)
{
    yield return GetMoreErrors(card);

    // further yield returns for more validation errors
}

Del mismo modo, se puede suprimir la función de envoltura alrededor DoGetErrors y sólo se mueven a la UnWrap callsite.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top