unity - yield return list c#
Retorno de rendimiento anidado con IEnumerable (6)
Tengo la siguiente función para obtener errores de validación para una tarjeta. Mi pregunta se relaciona con tratar con GetErrors. Ambos métodos tienen el mismo tipo de devolución 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 devolver todos los errores en GetMoreErrors
sin tener que enumerar a través de ellos?
Pensando en esto, esta es probablemente una pregunta estúpida, pero quiero asegurarme de que no me estoy equivocando.
Definitivamente no es una pregunta estúpida, ¡y es algo que F # admite con yield!
para una colección completa versus yield
para un solo artículo. (Eso puede ser muy útil en términos de recursividad de cola ...)
Lamentablemente no es compatible con C #.
Sin embargo, si tiene varios métodos y cada uno devuelve un IEnumerable<ErrorInfo>
, puede usar Enumerable.Concat
para simplificar su código:
private static IEnumerable<ErrorInfo> GetErrors(Card card)
{
return GetMoreErrors(card).Concat(GetOtherErrors())
.Concat(GetValidationErrors())
.Concat(AnyMoreErrors())
.Concat(ICantBelieveHowManyErrorsYouHave());
}
Sin embargo, hay una diferencia muy importante entre las dos implementaciones: esta llamará a todos los métodos de inmediato , aunque solo usará los iteradores devueltos uno a la vez. Su código existente esperará hasta que se GetMoreErrors()
en todo en GetMoreErrors()
incluso antes de que pregunte sobre los próximos errores.
Por lo general, esto no es importante, pero vale la pena entender qué sucederá cuando.
Me sorprende que nadie haya pensado en recomendar un método de extensión simple en IEnumerable<IEnumerable<T>>
para hacer que este código mantenga su ejecución diferida. Soy fan de la ejecución diferida por muchas razones, una de ellas es que la huella de memoria es pequeña incluso para enumerables enormes y monógamos.
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 podrías usarlo en tu caso como este
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, puede eliminar la función de envoltura alrededor de DoGetErrors
y simplemente mover UnWrap
al callsite.
No veo nada de malo en tu función, diría que está haciendo lo que quieres.
Piense en el rendimiento como la devolución de un elemento en la enumeración final cada vez que se invoca, por lo que cuando lo tiene en el bucle foreach así, cada vez que se invoca devuelve 1 elemento. Usted tiene la capacidad de poner declaraciones condicionales en su foreach para filtrar el conjunto de resultados. (simplemente al no ceder a sus criterios de exclusión)
Si agrega rendimientos posteriores más adelante en el método, continuará agregando 1 elemento a la enumeración, lo que permite 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;
}
}
}
Puede configurar todas las fuentes de error como esta (nombres de método 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();
}
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;
}
Alternativamente, podría 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.
Sí, es posible devolver todos los errores a la vez. Simplemente devuelva una List<T>
o ReadOnlyCollection<T>
.
Al devolver un IEnumerable<T>
estás devolviendo una secuencia de algo. En la superficie que puede parecer idéntico a devolver la colección, pero hay una serie de diferencias, debe tener en cuenta.
Colecciones
- La persona que llama puede estar segura de que tanto la colección como todos los elementos existirán cuando se devuelva la colección. Si la colección debe crearse por llamada, devolver una colección es una muy mala idea.
- La mayoría de las colecciones se pueden modificar cuando se devuelven.
- La colección es de tamaño finito.
Secuencias
- Se puede enumerar, y eso es prácticamente todo lo que podemos decir con certeza.
- Una secuencia devuelta en sí no puede ser modificada.
- Cada elemento se puede crear como parte de la ejecución de la secuencia (es decir, devolver
IEnumerable<T>
permite la evaluación diferida, y laList<T>
no lo hace). - Una secuencia puede ser infinita y así dejar que la persona que llama decida cuántos elementos se deben devolver.
Se me ocurrió un fragmento de yield_
rápido:
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>