Acesso Enumerator dentro de um loop foreach?
-
11-09-2019 - |
Pergunta
Eu tenho uma classe List, e eu gostaria de substituir GetEnumerator()
para retornar a minha própria classe Enumerator. Esta classe Enumerator teria duas propriedades adicionais que seriam atualizados como o Enumerator é usado.
Para simplificar (este não é o caso de negócio exato), digamos que essas propriedades foram CurrentIndex
e RunningTotal
.
Eu poderia gerir estas propriedades dentro do loop foreach manualmente, mas eu preferiria encapsular essa funcionalidade para reutilização, ea Enumerator parece ser o lugar certo.
O problema: foreach peles todo o negócio Enumerator, assim há uma maneira de, em uma instrução foreach, o acesso a corrente Enumerator para que eu possa recuperar minhas propriedades? Ou eu teria que foreach, use um velho desagradável enquanto loop, e manipular o Enumerator me?
Solução
Estritamente falando, eu diria que, se você quer fazer exatamente o que você está dizendo, então sim, você precisa chamar GetEnumerator e controlar o recenseador-se com um tempo loop.
Sem saber muito sobre suas necessidades de negócios, você pode ser capaz de tirar proveito de uma função de iterador, como algo parecido com isto:
public static IEnumerable<decimal> IgnoreSmallValues(List<decimal> list)
{
decimal runningTotal = 0M;
foreach (decimal value in list)
{
// if the value is less than 1% of the running total, then ignore it
if (runningTotal == 0M || value >= 0.01M * runningTotal)
{
runningTotal += value;
yield return value;
}
}
}
Em seguida, você pode fazer isso:
List<decimal> payments = new List<decimal>() {
123.45M,
234.56M,
.01M,
345.67M,
1.23M,
456.78M
};
foreach (decimal largePayment in IgnoreSmallValues(payments))
{
// handle the large payments so that I can divert all the small payments to my own bank account. Mwahaha!
}
Atualizado:
Ok, então aqui está um follow-up com o que tenho chamado a minha solução "anzol". Agora, deixe-me acrescentar um aviso de que eu realmente não posso pensar em uma boa razão para fazer algo assim, mas sua situação pode ser diferente.
A idéia é que você simplesmente criar um "anzol" objeto (tipo de referência) que você passa para o seu iterador função. A função iterator manipula o objeto gancho de pesca, e uma vez que você ainda tem uma referência a ele em sua parte externa código, você tem visibilidade sobre o que está acontecendo:
public class FishingHook
{
public int Index { get; set; }
public decimal RunningTotal { get; set; }
public Func<decimal, bool> Criteria { get; set; }
}
public static IEnumerable<decimal> FishingHookIteration(IEnumerable<decimal> list, FishingHook hook)
{
hook.Index = 0;
hook.RunningTotal = 0;
foreach(decimal value in list)
{
// the hook object may define a Criteria delegate that
// determines whether to skip the current value
if (hook.Criteria == null || hook.Criteria(value))
{
hook.RunningTotal += value;
yield return value;
hook.Index++;
}
}
}
Você iria utilizá-lo como este:
List<decimal> payments = new List<decimal>() {
123.45M,
.01M,
345.67M,
234.56M,
1.23M,
456.78M
};
FishingHook hook = new FishingHook();
decimal min = 0;
hook.Criteria = x => x > min; // exclude any values that are less than/equal to the defined minimum
foreach (decimal value in FishingHookIteration(payments, hook))
{
// update the minimum
if (value > min) min = value;
Console.WriteLine("Index: {0}, Value: {1}, Running Total: {2}", hook.Index, value, hook.RunningTotal);
}
// Resultint output is:
//Index: 0, Value: 123.45, Running Total: 123.45
//Index: 1, Value: 345.67, Running Total: 469.12
//Index: 2, Value: 456.78, Running Total: 925.90
// we've skipped the values .01, 234.56, and 1.23
Essencialmente, o objeto FishingHook dá-lhe algum controle sobre como as executa iterador. A impressão que tive da questão era que você precisava de alguma maneira para acessar o funcionamento interno do iterador para que você possa manipular como ele itera enquanto você estiver no meio de iteração, mas se isso não for o caso, então esta solução poder ser um exagero para o que você precisa.
Outras dicas
Com foreach
você na verdade não pode obter o recenseador - você poderia, no entanto, ter o retorno recenseador (yield
) uma tupla que inclui que os dados; na verdade, você provavelmente poderia usar LINQ para fazer isso por você ...
(eu não poderia limpa obter o índice usando LINQ - pode obter o total e valor atual via Aggregate
, embora, por isso aqui é a abordagem tuple)
using System.Collections;
using System.Collections.Generic;
using System;
class MyTuple
{
public int Value {get;private set;}
public int Index { get; private set; }
public int RunningTotal { get; private set; }
public MyTuple(int value, int index, int runningTotal)
{
Value = value; Index = index; RunningTotal = runningTotal;
}
static IEnumerable<MyTuple> SomeMethod(IEnumerable<int> data)
{
int index = 0, total = 0;
foreach (int value in data)
{
yield return new MyTuple(value, index++,
total = total + value);
}
}
static void Main()
{
int[] data = { 1, 2, 3 };
foreach (var tuple in SomeMethod(data))
{
Console.WriteLine("{0}: {1} ; {2}", tuple.Index,
tuple.Value, tuple.RunningTotal);
}
}
}
Você também pode fazer algo parecido com isso de uma forma mais funcional, dependendo de suas necessidades. O que você está pedindo pode ser pensada como "zipar" juntos várias seqüências, e depois iteração através de todos eles ao mesmo tempo. As três sequências para o exemplo que você deu seria:
- A seqüência de "valor"
- A seqüência de "index"
- O "Running Total" Sequence
O próximo passo seria a de especificar cada uma dessas seqüências separadamente:
List<decimal> ValueList
var Indexes = Enumerable.Range(0, ValueList.Count)
A última é mais divertido ... os dois métodos que posso pensar são: ou tem uma variável temporária usada para resumir a sequência, ou para recalcular a soma para cada item. A segunda é, obviamente, muito menos eficaz, eu prefiro usar o temporária:
decimal Sum = 0;
var RunningTotals = ValueList.Select(v => Sum = Sum + v);
O último passo seria fechar todos estes juntos. .Net 4 terá a operador postal construído em , caso em que será parecido com este:
var ZippedSequence = ValueList.Zip(Indexes, (value, index) => new {value, index}).Zip(RunningTotals, (temp, total) => new {temp.value, temp.index, total});
Isto, obviamente, fica mais ruidoso quanto mais as coisas que você tenta zip juntos.
No último elo, não há fonte para implementar a função Zip-se. É realmente um pouco simples de código.