Est-ce que Class doit implémenter IEnumerable pour utiliser Foreach?
-
02-07-2019 - |
Question
Ceci est en C #, j'ai une classe que j'utilise à partir de la DLL d'un autre. Il n'implémente pas IEnumerable mais dispose de 2 méthodes qui renvoient un IEnumerator. Est-il possible d'utiliser une boucle foreach sur ces éléments? La classe que j'utilise est scellée.
La solution
foreach
n'est pas nécessaire IEnumerable
, contrairement à la croyance populaire. Tout ce qu’il faut, c’est une méthode GetEnumerator
qui renvoie tout objet ayant la méthode MoveNext
et la propriété get Current
avec les signatures appropriées.
/ EDIT: Dans votre cas, toutefois, vous n’avez pas de chance. Vous pouvez toutefois trivialement envelopper votre objet pour le rendre énumérable:
class EnumerableWrapper {
private readonly TheObjectType obj;
public EnumerableWrapper(TheObjectType obj) {
this.obj = obj;
}
public IEnumerator<YourType> GetEnumerator() {
return obj.TheMethodReturningTheIEnumerator();
}
}
// Called like this:
foreach (var xyz in new EnumerableWrapper(yourObj))
…;
/ EDIT: la méthode suivante, proposée par plusieurs personnes, ne fonctionne pas si la méthode renvoie un IEnumerator
:
foreach (var yz in yourObj.MethodA())
…;
Autres conseils
Re: Si foreach ne nécessite pas de contrat d’interface explicite, trouve-t-il GetEnumerator en utilisant la réflexion?
(Je ne peux pas commenter car je n'ai pas assez de réputation.)
Si vous sous-entendez une exécution , alors non. Un autre fait moins connu est qu'il vérifie également si l'objet renvoyé que peut implémenter IEnumerator est disponible.
Pour voir cela en action, considérez cet extrait (exécutable).
using System;
using System.Collections.Generic;
using System.Text;
namespace ConsoleApplication3
{
class FakeIterator
{
int _count;
public FakeIterator(int count)
{
_count = count;
}
public string Current { get { return "Hello World!"; } }
public bool MoveNext()
{
if(_count-- > 0)
return true;
return false;
}
}
class FakeCollection
{
public FakeIterator GetEnumerator() { return new FakeIterator(3); }
}
class Program
{
static void Main(string[] args)
{
foreach (string value in new FakeCollection())
Console.WriteLine(value);
}
}
}
Selon MSDN :
foreach (type identifier in expression) statement
où expression est:
Collection d'objets ou expression de tableau. Le type de l'élément de collection doit être convertible en identifiant type. Ne pas utiliser une expression qui évalue à null. Evalue à un type qui implémente IEnumerable ou un type qui déclare une méthode GetEnumerator. Dans ce dernier cas, GetEnumerator devrait soit renvoyer un type qui implémente IEnumerator ou déclare tout les méthodes définies dans IEnumerator.
Réponse courte:
Vous avez besoin d'une classe avec une méthode nommée GetEnumerator , qui renvoie l'IEnumerator que vous avez déjà. Réalisez ceci avec un simple wrapper:
class ForeachWrapper
{
private IEnumerator _enumerator;
public ForeachWrapper(Func<IEnumerator> enumerator)
{
_enumerator = enumerator;
}
public IEnumerator GetEnumerator()
{
return _enumerator();
}
}
Utilisation:
foreach (var element in new ForeachWrapper(x => myClass.MyEnumerator()))
{
...
}
Extrait de la Spécification du langage C # . :
Le traitement au moment de la compilation d'un La déclaration foreach détermine d’abord la type de collection, type d'énumérateur et type d'élément de l'expression. Ce la détermination se déroule comme suit:
Si le type X de l'expression est un type tableau, alors il existe un implicite conversion de référence de X à la System.Collections.IEnumerable interface (depuis System.Array implémente cette interface). le le type de collection est le System.Collections.IEnumerable interface, le type énumérateur est le System.Collections.IEnumerator l'interface et le type d'élément est le type d'élément du type tableau X.
Sinon, déterminez si le type X a une valeur appropriée Méthode GetEnumerator:
Effectue une recherche de membre sur le type X avec l'identifiant GetEnumerator et no arguments de type. Si le membre cherche ne produit pas de correspondance, ou produit une ambiguïté, ou produit un correspond ce n'est pas un groupe de méthodes, vérifier une interface énumérable comme décrit ci-dessous. C'est recommandé qu'un avertissement soit émis si le membre la recherche produit n'importe quoi sauf un groupe de méthodes ou pas de correspondance.
Effectuez la résolution de la surcharge à l'aide du groupe de méthodes résultant et d'un liste d'arguments vide. En cas de surcharge résultats de résolution non applicables méthodes, entraîne une ambiguïté, ou se traduit par une seule meilleure méthode, mais cette méthode est statique ou non public, recherchez un énumérable interface comme décrit ci-dessous. Il est recommandé qu'un avertissement soit émis si la résolution de surcharge produit tout sauf un public sans ambiguïté méthode d'instance ou non applicable méthodes.
Si le type de retour E de la méthode GetEnumerator n'est pas une classe, type de structure ou d'interface, une erreur est produit et pas d'autres étapes sont prises.
La recherche de membre est effectuée sur E avec l'identificateur Current et no arguments de type. Si le membre cherche ne produit aucune correspondance, le résultat est un erreur, ou le résultat est n'importe quoi sauf une propriété d'instance publique qui permet la lecture, une erreur est produite et aucune autre mesure n'est prise.
La recherche de membre est effectuée sur E avec l'identifiant MoveNext et no arguments de type. Si le membre cherche ne produit aucune correspondance, le résultat est un erreur, ou le résultat est n'importe quoi sauf un groupe de méthodes, une erreur est produit et pas d'autres étapes sont prises.
La résolution de surcharge est effectuée sur le groupe de méthodes avec un vide liste d'arguments. Si la résolution de surcharge n'entraîne aucune méthode applicable, entraîne une ambiguïté, ou aboutit à une seule meilleure méthode, mais cette méthode est statique ou non public, ou son le type de retour n'est pas bool, une erreur est produit et pas d'autres étapes sont prises.
Le type de collection est X, le type énumérateur est E et l'élément type est le type du courant propriété.
Sinon, recherchez une interface énumérable:
S'il existe exactement un type T tel qu'il existe un implicite conversion de X à l'interface System.Collections.Generic.IEnumerable & Lt; T & Gt ;, alors le type de collection est-ce interface, le type énumérateur est le interface System.Collections.Generic.IEnumerator & Lt; T & Gt ;, et le type d'élément est T.
Sinon, s'il y a plus d'un tel type T, alors une erreur est produit et pas d'autres étapes sont prises.
Sinon, s'il y a une impliconversion de cit de X à la System.Collections.IEnumerable interface, alors le type de collection est cette interface, le type énumérateur est L'interface System.Collections.IEnumerator et le type d'élément est object.
Sinon, une erreur est générée et aucune autre mesure n'est prise.
Pas strictement. Tant que la classe a les membres GetEnumerator, MoveNext, Reset et Current requis, elle fonctionnera avec foreach
Non, vous n'avez même pas besoin d'une méthode GetEnumerator, par exemple:
.class Counter
{
public IEnumerable<int> Count(int max)
{
int i = 0;
while (i <= max)
{
yield return i;
i++;
}
yield break;
}
}
qui s'appelle de cette façon:
Counter cnt = new Counter();
foreach (var i in cnt.Count(6))
{
Console.WriteLine(i);
}
Vous pouvez toujours l’envelopper, et en plus, vous devez être & "accessible" & "; il vous suffit d'avoir une méthode appelée & "GetEnumerator &"; avec la signature appropriée.
class EnumerableAdapter
{
ExternalSillyClass _target;
public EnumerableAdapter(ExternalSillyClass target)
{
_target = target;
}
public IEnumerable GetEnumerator(){ return _target.SomeMethodThatGivesAnEnumerator(); }
}
Étant donné la classe X avec les méthodes A et B qui renvoient toutes les deux IEnumerable, vous pouvez utiliser un foreach sur la classe comme ceci:
foreach (object y in X.A())
{
//...
}
// or
foreach (object y in X.B())
{
//...
}
Vraisemblablement, la signification des énumérables renvoyés par A et B est bien définie.
@Brian: Vous n'êtes pas sûr d'essayer de boucler sur la valeur renvoyée par l'appel de la méthode ou sur la classe elle-même, Si ce que vous voulez est la classe, faites-en un tableau que vous pouvez utiliser avec foreach.
Pour qu'une classe soit utilisable avec foeach, il suffit d'avoir une méthode publique qui retourne et IEnumerator nommée GetEnumerator (), c'est tout:
Prenez la classe suivante, elle n'implémente pas IEnumerable ou IEnumerator:
public class Foo
{
private int[] _someInts = { 1, 2, 3, 4, 5, 6 };
public IEnumerator GetEnumerator()
{
foreach (var item in _someInts)
{
yield return item;
}
}
}
Sinon, la méthode GetEnumerator () pourrait être écrite:
public IEnumerator GetEnumerator()
{
return _someInts.GetEnumerator();
}
Lorsqu'il est utilisé dans un foreach (notez que le non wrapper est utilisé, il s'agit simplement d'une instance de classe):
foreach (int item in new Foo())
{
Console.Write("{0,2}",item);
}
imprime:
1 2 3 4 5 6
Le type nécessite uniquement d'avoir une méthode publique / non statique / non générique / sans paramètre nommée GetEnumerator
qui devrait renvoyer quelque chose qui possède une méthode MoveNext
publique et une propriété publique Current
. Si je me souviens bien de M. Eric Lippert quelque part, cela a été conçu de manière à prendre en compte l'ère pré-générique pour les problèmes de performance liés à la sécurité de type et à la boxe dans le cas de types de valeur.
Par exemple, cela fonctionne:
class Test
{
public SomethingEnumerator GetEnumerator()
{
}
}
class SomethingEnumerator
{
public Something Current //could return anything
{
get { }
}
public bool MoveNext()
{
}
}
//now you can call
foreach (Something thing in new Test()) //type safe
{
}
Ceci est ensuite traduit par le compilateur en:
var enumerator = new Test().GetEnumerator();
try {
Something element; //pre C# 5
while (enumerator.MoveNext()) {
Something element; //post C# 5
element = (Something)enumerator.Current; //the cast!
statement;
}
}
finally {
IDisposable disposable = enumerator as System.IDisposable;
if (disposable != null) disposable.Dispose();
}
de la section 8.8.4 de la spécification .
Il est intéressant de noter la priorité de l'énumérateur impliquée - c'est comme si vous utilisiez une méthode public GetEnumerator
. Il s'agit du choix par défaut de foreach
quel que soit le système qui l'implémente. Par exemple:
class Test : IEnumerable<int>
{
public SomethingEnumerator GetEnumerator()
{
//this one is called
}
IEnumerator<int> IEnumerable<int>.GetEnumerator()
{
}
}
( Si vous n'avez pas d'implémentation publique (c'est-à-dire seulement une implémentation explicite), la priorité est alors comme IEnumerator<T>
> IEnumerator
. )