Eu tenho um IEnumerable<T>
método que estou usando para encontrar controles em uma página WebForms.
O método é recursivo e estou tendo alguns problemas para retornar o tipo desejado quando o yield return
retorno é o valor da chamada recursiva.
Meu código é o seguinte:
public static IEnumerable<Control>
GetDeepControlsByType<T>(this Control control)
{
foreach(Control c in control.Controls)
{
if (c is T)
{
yield return c;
}
if(c.Controls.Count > 0)
{
yield return c.GetDeepControlsByType<T>();
}
}
}
No momento, isso gera um erro "Não é possível converter o tipo de expressão". Se, no entanto, esse método retornar tipo IEnumerable<Object>
, o código será gerado, mas o tipo errado será retornado na saída.
Existe uma maneira de usar yield return
enquanto também usa recursão?
c#
generics
ienumerable
yield
Jamie Dixon
fonte
fonte
if(c.Controls.Count > 0)
->if(c.Controls.Any())
, especialmente se você está rendendo bem :)yield
. Por favor, veja abaixo :) E é um one-liner também :)yield return
em funções recursivas, o uso da memória aumenta de forma explosiva. Veja stackoverflow.com/a/30300257/284795Respostas:
Dentro de um método que retorna
IEnumerable<T>
,yield return
tem que retornarT
, não umIEnumerable<T>
.Substituir
com:
fonte
Você precisa produzir cada um dos itens gerados pela chamada recursiva:
Observe que há um custo para se repetir dessa maneira - você acabará criando muitos iteradores, o que pode criar um problema de desempenho se você tiver uma árvore de controle realmente profunda. Se você deseja evitar isso, basicamente precisa fazer a recursão dentro do método, para garantir que haja apenas um iterador (máquina de estado) criado. Veja esta pergunta para obter mais detalhes e uma implementação de amostra - mas isso obviamente também adiciona uma certa complexidade.
fonte
c.Controls.Count > 0
vs..Any()
:)Como Jon Skeet e Coronel Panic observam em suas respostas, o uso de
yield return
métodos recursivos pode causar problemas de desempenho se a árvore for muito profunda.Aqui está um método genérico de extensão não recursiva que executa uma travessia profunda de uma sequência de árvores:
Diferentemente da solução de Eric Lippert , o RecursiveSelect trabalha diretamente com enumeradores para que não precise chamar Reverse (que armazena em buffer toda a sequência na memória).
Usando RecursiveSelect, o método original do OP pode ser reescrito da seguinte maneira:
fonte
Outros forneceram a resposta correta, mas não creio que seu caso seja beneficiado.
Aqui está um trecho que alcança o mesmo sem ceder.
fonte
yield
também? ;)foreach
loop adicional . Agora eu posso fazer isso com pura programação funcional!Você precisa retornar os itens do enumerador, não do próprio enumerador, no seu segundo
yield return
fonte
Eu acho que você tem que render retornar cada um dos controles nos enumeráveis.
fonte
A sintaxe de Seredynski está correta, mas você deve evitar as
yield return
funções recursivas, pois é um desastre para o uso da memória. Consulte https://stackoverflow.com/a/3970171/284795, que escala de forma explosiva com a profundidade (uma função semelhante estava usando 10% da memória no meu aplicativo).Uma solução simples é usar uma lista e passá-la com a recursão https://codereview.stackexchange.com/a/5651/754
Como alternativa, você pode usar uma pilha e um loop while para eliminar chamadas recursivas https://codereview.stackexchange.com/a/5661/754
fonte
Embora haja muitas boas respostas por aí, eu ainda acrescentaria que é possível usar os métodos LINQ para realizar a mesma coisa.
Por exemplo, o código original do OP pode ser reescrito como:
fonte
OfType
fato não é realmente diferente. No máximo, uma pequena mudança estilística. Um controle não pode ser filho de vários controles; portanto, a árvore atravessada já não é necessária . Usar emUnion
vez deConcat
verificar desnecessariamente a exclusividade de uma sequência já garantida como única e, portanto, é um downgrade objetivo.