À quoi sert le mot clé yield en C #?


829

Dans la question Comment puis-je exposer uniquement un fragment d'IList <>, l' une des réponses contenait l'extrait de code suivant:

IEnumerable<object> FilteredList()
{
    foreach(object item in FullList)
    {
        if(IsItemInPartialList(item))
            yield return item;
    }
}

Que fait le mot clé yield ici? Je l'ai vu référencé à quelques endroits, et une autre question, mais je n'ai pas tout à fait compris ce qu'il fait réellement. J'ai l'habitude de penser au rendement dans le sens où un fil cède à un autre, mais cela ne semble pas pertinent ici.



14
Ce n'est pas surprenant. La confusion vient du fait que nous sommes conditionnés à voir le «retour» comme une sortie de fonction alors que précédé d'un «rendement» il ne l'est pas.
Larry

4
J'ai lu les documents mais j'ai bien peur de ne toujours pas comprendre :(
Ortund

Réponses:


737

Le yieldmot-clé fait en fait beaucoup ici.

La fonction renvoie un objet qui implémente l' IEnumerable<object>interface. Si une fonction appelante démarre foreachsur cet objet, la fonction est appelée à nouveau jusqu'à ce qu'elle "cède". Il s'agit du sucre syntaxique introduit en C # 2.0 . Dans les versions précédentes, vous deviez créer vos propres objets IEnumerableet IEnumeratorfaire des choses comme ça.

La façon la plus simple de comprendre un code comme celui-ci est de saisir un exemple, de définir des points d'arrêt et de voir ce qui se passe. Essayez de parcourir cet exemple:

public void Consumer()
{
    foreach(int i in Integers())
    {
        Console.WriteLine(i.ToString());
    }
}

public IEnumerable<int> Integers()
{
    yield return 1;
    yield return 2;
    yield return 4;
    yield return 8;
    yield return 16;
    yield return 16777216;
}

Lorsque vous parcourez l'exemple, vous trouverez le premier appel à Integers()retours 1. Le deuxième appel revient 2et la ligne yield return 1n'est pas exécutée à nouveau.

Voici un exemple concret:

public IEnumerable<T> Read<T>(string sql, Func<IDataReader, T> make, params object[] parms)
{
    using (var connection = CreateConnection())
    {
        using (var command = CreateCommand(CommandType.Text, sql, connection, parms))
        {
            command.CommandTimeout = dataBaseSettings.ReadCommandTimeout;
            using (var reader = command.ExecuteReader())
            {
                while (reader.Read())
                {
                    yield return make(reader);
                }
            }
        }
    }
}

113
Dans ce cas, ce serait plus facile, j'utilise simplement l'entier ici pour montrer comment fonctionne le rendement du rendement. Ce qui est bien avec l'utilisation du rendement, c'est que c'est un moyen très rapide d'implémenter le modèle d'itérateur, donc les choses sont évaluées paresseusement.
Mendelt

111
Il convient également de noter que vous pouvez l'utiliser yield break;lorsque vous ne souhaitez plus renvoyer d'articles.
Rory

7
yieldn'est pas un mot-clé. Si c'était le cas, je ne pourrais pas utiliser le rendement comme identifiant comme dansint yield = 500;
Brandin

5
@Brandin, c'est parce que tous les langages de programmation prennent en charge deux types de mots clés, à savoir réservés et contextuels. yield tombe dans la dernière catégorie, c'est pourquoi votre code n'est pas interdit par le compilateur C #. Plus de détails ici: ericlippert.com/2009/05/11/reserved-and-contextual-keywords Vous seriez ravis de savoir qu'il existe également des mots réservés qui ne sont pas reconnus comme mots clés par une langue. Par exemple goto en java. Plus de détails ici: stackoverflow.com/questions/2545103/…
RBT

7
'If a calling function starts foreach-ing over this object the function is called again until it "yields"'. ne me semble pas juste. J'ai toujours pensé au mot-clé c # yield dans le contexte de "la récolte donne une récolte abondante", au lieu de "la voiture cède au piéton".
Zack

369

Itération. Il crée une machine d'état "sous les couvertures" qui se souvient où vous étiez à chaque cycle supplémentaire de la fonction et reprend à partir de là.


210

Le rendement a deux grandes utilisations,

  1. Il permet de fournir une itération personnalisée sans créer de collections temporaires.

  2. Cela aide à effectuer une itération avec état. entrez la description de l'image ici

Afin d'expliquer ci-dessus deux points de manière plus démonstrative, j'ai créé une vidéo simple que vous pouvez regarder ici


13
La vidéo m'aide à bien comprendre le yield. @ Article de projet de code de ShivprasadKoirala Quelle est l'utilisation de C # Yield? de la même explication est également une bonne source
Dush

J'ajouterais également un troisième point qui yieldest un moyen "rapide" de créer un IEnumerator personnalisé (plutôt qu'une classe implémente l'interface IEnumerator).
MrTourkos

J'ai regardé votre vidéo Shivprasad et elle expliquait clairement l'utilisation du mot clé yield.
Tore Aurstad

Merci pour la vidéo!. Très bien expliqué!
Roblogic

Excellente vidéo, mais étonnant ... L'implémentation utilisant yield est évidemment plus propre, mais elle doit essentiellement créer sa propre mémoire temporaire ou / et List en interne afin de garder une trace de l'état (ou plutôt de créer une machine d'état). Alors, "Yield" fait-il autre chose que de simplifier l'implémentation et d'améliorer les choses ou y a-t-il autre chose? Qu'en est-il de l'efficacité, l'exécution de code utilisant Yield est-elle plus ou moins efficace / rapide que sans?
hardQuestions

135

Récemment, Raymond Chen a également publié une intéressante série d'articles sur le mot-clé yield.

Bien qu'il soit nominalement utilisé pour implémenter facilement un modèle d'itérateur, mais peut être généralisé dans une machine à états. Inutile de citer Raymond, la dernière partie contient également des liens vers d'autres utilisations (mais l'exemple du blog d'Entin est particulièrement bon, montrant comment écrire du code sécurisé asynchrone).


Cela doit être voté. Doux comment il explique le but de l'opérateur et des internes.
sajidnizami

3
la partie 1 explique le sucre syntaxique du «rendement de retour». excellente explication!
Dror Weiss

99

À première vue, return return est un sucre .NET pour renvoyer un IEnumerable .

Sans rendement, tous les articles de la collection sont créés à la fois:

class SomeData
{
    public SomeData() { }

    static public IEnumerable<SomeData> CreateSomeDatas()
    {
        return new List<SomeData> {
            new SomeData(), 
            new SomeData(), 
            new SomeData()
        };
    }
}

Même code en utilisant yield, il retourne article par article:

class SomeData
{
    public SomeData() { }

    static public IEnumerable<SomeData> CreateSomeDatas()
    {
        yield return new SomeData();
        yield return new SomeData();
        yield return new SomeData();
    }
}

L'avantage de l'utilisation de yield est que si la fonction consommant vos données a simplement besoin du premier élément de la collection, les autres éléments ne seront pas créés.

L'opérateur de rendement permet la création d'articles selon les besoins. C'est une bonne raison de l'utiliser.


40

yield returnest utilisé avec les énumérateurs. À chaque appel de déclaration de rendement, le contrôle est retourné à l'appelant mais il garantit que l'état de l'appelé est maintenu. Pour cette raison, lorsque l'appelant énumère l'élément suivant, il continue l'exécution dans la méthode appelée de l'instruction immédiatement après l' yieldinstruction.

Essayons de comprendre cela avec un exemple. Dans cet exemple, correspondant à chaque ligne, j'ai mentionné l'ordre dans lequel s'exécute l'exécution.

static void Main(string[] args)
{
    foreach (int fib in Fibs(6))//1, 5
    {
        Console.WriteLine(fib + " ");//4, 10
    }            
}

static IEnumerable<int> Fibs(int fibCount)
{
    for (int i = 0, prevFib = 0, currFib = 1; i < fibCount; i++)//2
    {
        yield return prevFib;//3, 9
        int newFib = prevFib + currFib;//6
        prevFib = currFib;//7
        currFib = newFib;//8
    }
}

De plus, l'état est maintenu pour chaque énumération. Supposons que j'ai un autre appel à la Fibs()méthode, puis l'état sera réinitialisé pour cela.


2
set prevFib = 1 - le premier nombre de Fibonacci est un "1", pas un "0"
fubo

31

Intuitivement, le mot-clé renvoie une valeur de la fonction sans la quitter, c'est-à-dire que dans votre exemple de code, il renvoie la itemvaleur actuelle , puis reprend la boucle. Plus formellement, il est utilisé par le compilateur pour générer du code pour un itérateur . Les itérateurs sont des fonctions qui renvoient des IEnumerableobjets. Le MSDN a plusieurs articles à leur sujet.


4
Eh bien, pour être précis, il ne reprend pas la boucle, il l'interrompt jusqu'à ce que le parent appelle "iterator.next ()".
Alex

8
@jitbit C'est pourquoi j'ai utilisé «intuitivement» et «plus formellement».
Konrad Rudolph

31

Une implémentation de liste ou de tableau charge immédiatement tous les éléments tandis que l'implémentation de yield fournit une solution d'exécution différée.

Dans la pratique, il est souvent souhaitable d'effectuer le minimum de travail selon les besoins afin de réduire la consommation de ressources d'une application.

Par exemple, nous pouvons avoir une application qui traite des millions d'enregistrements à partir d'une base de données. Les avantages suivants peuvent être obtenus lorsque nous utilisons IEnumerable dans un modèle basé sur l'extraction à exécution différée:

  • L'évolutivité, la fiabilité et la prévisibilité sont susceptibles de s'améliorer car le nombre d'enregistrements n'affecte pas de manière significative les besoins en ressources de l'application.
  • Les performances et la réactivité sont susceptibles de s'améliorer car le traitement peut démarrer immédiatement au lieu d'attendre que la collection entière soit chargée en premier.
  • La récupérabilité et l'utilisation sont susceptibles de s'améliorer car l'application peut être arrêtée, démarrée, interrompue ou échouer. Seuls les éléments en cours seront perdus par rapport à la récupération préalable de toutes les données où seule une partie des résultats était réellement utilisée.
  • Le traitement continu est possible dans les environnements où des flux de charge de travail constants sont ajoutés.

Voici une comparaison entre construire une collection en premier comme une liste par rapport à l'utilisation de yield.

Exemple de liste

    public class ContactListStore : IStore<ContactModel>
    {
        public IEnumerable<ContactModel> GetEnumerator()
        {
            var contacts = new List<ContactModel>();
            Console.WriteLine("ContactListStore: Creating contact 1");
            contacts.Add(new ContactModel() { FirstName = "Bob", LastName = "Blue" });
            Console.WriteLine("ContactListStore: Creating contact 2");
            contacts.Add(new ContactModel() { FirstName = "Jim", LastName = "Green" });
            Console.WriteLine("ContactListStore: Creating contact 3");
            contacts.Add(new ContactModel() { FirstName = "Susan", LastName = "Orange" });
            return contacts;
        }
    }

    static void Main(string[] args)
    {
        var store = new ContactListStore();
        var contacts = store.GetEnumerator();

        Console.WriteLine("Ready to iterate through the collection.");
        Console.ReadLine();
    }

Sortie de la console
ContactListStore: création de contact 1
ContactListStore: création de contact 2
ContactListStore: création de contact 3
Prêt à parcourir la collection.

Remarque: La collection entière a été chargée en mémoire sans même demander un seul élément dans la liste

Exemple de rendement

public class ContactYieldStore : IStore<ContactModel>
{
    public IEnumerable<ContactModel> GetEnumerator()
    {
        Console.WriteLine("ContactYieldStore: Creating contact 1");
        yield return new ContactModel() { FirstName = "Bob", LastName = "Blue" };
        Console.WriteLine("ContactYieldStore: Creating contact 2");
        yield return new ContactModel() { FirstName = "Jim", LastName = "Green" };
        Console.WriteLine("ContactYieldStore: Creating contact 3");
        yield return new ContactModel() { FirstName = "Susan", LastName = "Orange" };
    }
}

static void Main(string[] args)
{
    var store = new ContactYieldStore();
    var contacts = store.GetEnumerator();

    Console.WriteLine("Ready to iterate through the collection.");
    Console.ReadLine();
}

Sortie console
Prêt à parcourir la collection.

Remarque: La collection n'a pas été exécutée du tout. Cela est dû à la nature «d'exécution différée» de IEnumerable. La construction d'un élément ne se produira que lorsqu'il est vraiment nécessaire.

Appelons à nouveau la collection et évitons le comportement lorsque nous récupérons le premier contact de la collection.

static void Main(string[] args)
{
    var store = new ContactYieldStore();
    var contacts = store.GetEnumerator();
    Console.WriteLine("Ready to iterate through the collection");
    Console.WriteLine("Hello {0}", contacts.First().FirstName);
    Console.ReadLine();
}

Sortie console
Prêt à parcourir la collection
ContactYieldStore: création d'un contact 1
Bonjour Bob

Agréable! Seul le premier contact a été établi lorsque le client a "retiré" l'article de la collection.


1
Cette réponse nécessite plus d'attention! Thx
leon22

@ leon22 absolument +2
snr

26

Voici un moyen simple de comprendre le concept: L'idée de base est que si vous voulez une collection sur laquelle vous pouvez utiliser " foreach", mais rassembler les éléments dans la collection coûte cher pour une raison quelconque (comme les interroger dans une base de données), ET vous n'aurez souvent pas besoin de l'intégralité de la collection, puis vous créez une fonction qui crée la collection un élément à la fois et la renvoie au consommateur (qui peut alors mettre fin à l'effort de collecte plus tôt).

Pensez-y de cette façon: vous allez au comptoir de viande et vous voulez acheter une livre de jambon tranché. Le boucher prend un jambon de 10 livres à l'arrière, le met sur la machine à trancher, tranche le tout, puis vous ramène le tas de tranches et en mesure une livre. (VIEILLE façon). Avec yield, le boucher amène la machine à trancher au comptoir et commence à trancher et à "céder" chaque tranche sur la balance jusqu'à ce qu'elle mesure 1 livre, puis l'enveloppe pour vous et vous avez terminé. L'Ancienne Voie peut être meilleure pour le boucher (lui permet d'organiser ses machines comme il le souhaite), mais la Nouvelle Voie est clairement plus efficace dans la plupart des cas pour le consommateur.


18

Le yieldmot-clé vous permet de créer un IEnumerable<T>dans le formulaire sur un bloc itérateur . Ce bloc itérateur prend en charge l' exécution différée et si vous n'êtes pas familier avec le concept, il peut sembler presque magique. Cependant, à la fin de la journée, c'est juste du code qui s'exécute sans astuces étranges.

Un bloc itérateur peut être décrit comme du sucre syntaxique où le compilateur génère une machine d'état qui garde une trace de la progression de l'énumération de l'énumérable. Pour énumérer un énumérable, vous utilisez souvent une foreachboucle. Cependant, une foreachboucle est également du sucre syntaxique. Vous êtes donc deux abstractions retirées du vrai code, c'est pourquoi il peut être difficile au début de comprendre comment tout cela fonctionne ensemble.

Supposons que vous ayez un bloc d'itérateur très simple:

IEnumerable<int> IteratorBlock()
{
    Console.WriteLine("Begin");
    yield return 1;
    Console.WriteLine("After 1");
    yield return 2;
    Console.WriteLine("After 2");
    yield return 42;
    Console.WriteLine("End");
}

Les vrais blocs d'itérateur ont souvent des conditions et des boucles, mais lorsque vous vérifiez les conditions et déroulez les boucles, ils finissent toujours comme des yieldinstructions entrelacées avec un autre code.

Pour énumérer le bloc itérateur, une foreachboucle est utilisée:

foreach (var i in IteratorBlock())
    Console.WriteLine(i);

Voici la sortie (pas de surprise ici):

Commencer
1
Après 1
2
Après 2
42
Fin

Comme indiqué ci foreach- dessus est le sucre syntaxique:

IEnumerator<int> enumerator = null;
try
{
    enumerator = IteratorBlock().GetEnumerator();
    while (enumerator.MoveNext())
    {
        var i = enumerator.Current;
        Console.WriteLine(i);
    }
}
finally
{
    enumerator?.Dispose();
}

Pour tenter de démêler cela, j'ai créé un diagramme de séquence avec les abstractions supprimées:

Diagramme de séquence de blocs d'itérateur C #

La machine à états générée par le compilateur implémente également l'énumérateur, mais pour rendre le diagramme plus clair, je les ai présentées comme des instances distinctes. (Lorsque la machine d'état est énumérée à partir d'un autre thread, vous obtenez en fait des instances distinctes mais ce détail n'est pas important ici.)

Chaque fois que vous appelez votre bloc itérateur, une nouvelle instance de la machine d'état est créée. Cependant, aucun de votre code dans le bloc itérateur n'est exécuté jusqu'à ce qu'il enumerator.MoveNext()s'exécute pour la première fois. Voici comment fonctionne l'exécution différée. Voici un exemple (plutôt idiot):

var evenNumbers = IteratorBlock().Where(i => i%2 == 0);

À ce stade, l'itérateur n'a pas exécuté. La Whereclause crée un nouveau IEnumerable<T>qui enveloppe le IEnumerable<T>retourné par IteratorBlockmais cet énumérable n'a pas encore été énuméré. Cela se produit lorsque vous exécutez une foreachboucle:

foreach (var evenNumber in evenNumbers)
    Console.WriteLine(eventNumber);

Si vous énumérez l'énumérable deux fois, une nouvelle instance de la machine d'état est créée à chaque fois et votre bloc itérateur exécutera le même code deux fois.

Notez que les méthodes de LINQ aiment ToList(), ToArray(), First(), Count()etc. utilisera une foreachboucle pour énumérer les dénombrable. Par exemple ToList(), énumérera tous les éléments de l'énumérable et les stockera dans une liste. Vous pouvez maintenant accéder à la liste pour obtenir tous les éléments de l'énumérable sans que le bloc d'itérateur ne s'exécute à nouveau. Il existe un compromis entre l'utilisation du processeur pour produire les éléments de l'énumération plusieurs fois et la mémoire pour stocker les éléments de l'énumération pour y accéder plusieurs fois lors de l'utilisation de méthodes telles que ToList().


18

Si je comprends bien, voici comment je formulerais cela du point de vue de la fonction implémentant IEnumerable avec yield.

  • En voici un.
  • Appelez à nouveau si vous en avez besoin d'un autre.
  • Je me souviendrai de ce que je t'ai déjà donné.
  • Je ne saurai que si je peux vous en donner un autre lorsque vous rappellerez.

simple et génial
Harry

10

Le mot clé C # yield, pour le dire simplement, permet de nombreux appels à un corps de code, appelé itérateur, qui sait comment retourner avant qu'il ne soit terminé et, lorsqu'il est rappelé, continue là où il s'était arrêté - c'est-à-dire qu'il aide un itérateur deviennent transparents avec état pour chaque élément dans une séquence que l'itérateur renvoie par appels successifs.

En JavaScript, le même concept est appelé Générateurs.


Meilleure explication pour le moment. S'agit-il également des mêmes générateurs en python?
petrosmm

7

C'est un moyen très simple et facile de créer un énumérable pour votre objet. Le compilateur crée une classe qui encapsule votre méthode et qui implémente, dans ce cas, IEnumerable <object>. Sans le mot-clé yield, vous devez créer un objet qui implémente IEnumerable <object>.


5

Il produit une séquence énumérable. Ce qu'il fait est en fait de créer une séquence IEnumerable locale et de la renvoyer comme résultat de méthode


3

Ce lien a un exemple simple

Des exemples encore plus simples sont ici

public static IEnumerable<int> testYieldb()
{
    for(int i=0;i<3;i++) yield return 4;
}

Notez que return return ne reviendra pas de la méthode. Vous pouvez même mettre WriteLineaprès layield return

Ce qui précède produit un IEnumerable de 4 pouces 4,4,4,4

Ici avec un WriteLine. Ajoutera 4 à la liste, imprimera abc, puis ajoutera 4 à la liste, puis terminera la méthode et reviendra donc vraiment de la méthode (une fois la méthode terminée, comme ce serait le cas avec une procédure sans retour). Mais cela aurait une valeur, une IEnumerableliste de ints, qu'il retourne à la fin.

public static IEnumerable<int> testYieldb()
{
    yield return 4;
    console.WriteLine("abc");
    yield return 4;
}

Notez également que lorsque vous utilisez yield, ce que vous retournez n'est pas du même type que la fonction. C'est du type d'un élément dans la IEnumerableliste.

Vous utilisez yield avec le type de retour de la méthode as IEnumerable. Si le type de retour de la méthode est intou List<int>et que vous utilisez yield, alors il ne sera pas compilé. Vous pouvez utiliser le IEnumerabletype de retour de méthode sans rendement, mais il semble que vous ne puissiez pas utiliser le rendement sans IEnumerabletype de retour de méthode.

Et pour l'exécuter, vous devez l'appeler d'une manière spéciale.

static void Main(string[] args)
{
    testA();
    Console.Write("try again. the above won't execute any of the function!\n");

    foreach (var x in testA()) { }


    Console.ReadLine();
}



// static List<int> testA()
static IEnumerable<int> testA()
{
    Console.WriteLine("asdfa");
    yield return 1;
    Console.WriteLine("asdf");
}

note - si vous essayez de comprendre SelectMany, il utilise le rendement et également des génériques .. cet exemple peut aider public static IEnumerable<TResult> testYieldc<TResult>(TResult t) { yield return t; }et public static IEnumerable<TResult> testYieldc<TResult>(TResult t) { return new List<TResult>(); }
barlop

On dirait une très bonne explication! Cela aurait pu être la réponse acceptée.
pongapundit

@pongapundit merci, ma réponse est certainement claire et simple, mais je n'ai pas beaucoup utilisé le rendement moi-même, d'autres répondeurs ont beaucoup plus d'expérience avec lui et une connaissance de ses utilisations que moi. Ce que j'ai écrit sur le rendement ici, c'était probablement en me grattant la tête en essayant de trouver certaines des réponses ici et sur ce lien dotnetperls! Mais comme je ne le sais pas yield returnbien (à part la simple chose que j'ai mentionnée), et que je ne l'ai pas beaucoup utilisé et que je ne sais pas grand-chose sur ses utilisations, je ne pense pas que cela devrait être celui qui est accepté.
barlop

3

Un point majeur sur le mot-clé Yield est l' exécution paresseuse . Maintenant, ce que je veux dire par exécution paresseuse est d'exécuter en cas de besoin. Une meilleure façon de le dire est de donner un exemple

Exemple: ne pas utiliser Yield, c'est-à-dire pas d'exécution paresseuse.

        public static IEnumerable<int> CreateCollectionWithList()
        {
            var list =  new List<int>();
            list.Add(10);
            list.Add(0);
            list.Add(1);
            list.Add(2);
            list.Add(20);

            return list;
        }

Exemple: utilisation de Yield ie Lazy Execution.

    public static IEnumerable<int> CreateCollectionWithYield()
    {
        yield return 10;
        for (int i = 0; i < 3; i++) 
        {
            yield return i;
        }

        yield return 20;
    }

Maintenant, quand j'appelle les deux méthodes.

var listItems = CreateCollectionWithList();
var yieldedItems = CreateCollectionWithYield();

vous remarquerez que listItems contiendra 5 éléments (passez votre souris sur listItems pendant le débogage). Alors que yieldItems aura juste une référence à la méthode et non aux articles. Cela signifie qu'il n'a pas exécuté le processus d'obtention d'éléments dans la méthode. Un moyen très efficace d'obtenir des données uniquement en cas de besoin. La mise en œuvre réelle du rendement peut être vue dans ORM comme Entity Framework et NHibernate etc.


-3

Il essaie d'apporter de la bonté Ruby :)
Concept: Ceci est un exemple de code Ruby qui imprime chaque élément du tableau

 rubyArray = [1,2,3,4,5,6,7,8,9,10]
    rubyArray.each{|x| 
        puts x   # do whatever with x
    }

Chaque implémentation de méthode du tableau donne le contrôle à l'appelant (le «met x») avec chaque élément du tableau soigneusement présenté comme x. L'appelant peut alors faire tout ce qu'il doit faire avec x.

Cependant .Net ne va pas jusqu'au bout ici. C # semble avoir couplé le rendement avec IEnumerable, d'une manière qui vous oblige à écrire une boucle foreach dans l'appelant comme vu dans la réponse de Mendelt. Un peu moins élégant.

//calling code
foreach(int i in obCustomClass.Each())
{
    Console.WriteLine(i.ToString());
}

// CustomClass implementation
private int[] data = {1,2,3,4,5,6,7,8,9,10};
public IEnumerable<int> Each()
{
   for(int iLooper=0; iLooper<data.Length; ++iLooper)
        yield return data[iLooper]; 
}

7
-1 Cette réponse ne me semble pas juste. Oui, C # yieldest couplé à IEnumerable, et C # n'a pas le concept Ruby d'un "bloc". Mais C # a des lambdas, ce qui pourrait permettre l'implémentation d'une ForEachméthode, tout comme Ruby each. Cela ne signifie pas pour autant que ce serait une bonne idée de le faire .
rsenna

Mieux encore: public IEnumerable <int> Each () {int index = 0; return return data [index ++]; }
ata
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.