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.

Était-ce utile?

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. )

.
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top