diff --git a/Enumerables-Technet/Article/article-en.md b/Enumerables-Technet/Article/article-en.md new file mode 100644 index 0000000..a25c683 --- /dev/null +++ b/Enumerables-Technet/Article/article-en.md @@ -0,0 +1,878 @@ +# Enumerable in deep + +This article discusses in details about the enumerables in C#. + +It based on french articles written [on this blog](http://www.yeg-projects.com/2015/01/soyons-enumerables-partie-1/) by Yan Grenier. + +All code samples are in the project "Enumerable-en" in the solution. + +# The enumerables + +The purpose of the enumerables is to browse through a list of elements by retreiving element by element like a stream (named as iteration or enumeration). The most common usage of this principle is done with the `foreach` keyword. + +In .Net the enumerables are based on the interfaces `System.Collections.IEnumerable` and `System.Collections.IEnumerator`. The generic version of these interfaces exist as `System.Collections.Generic.IEnumerable<>` and `System.Collections.Generic.IEnumerator<>`. + +When a class implements the interface `IEnumerable` then it becomes enumerable. + +`IEnumerable` indicates than we can enumerate an objet that is responsible to provide an `IEnumerator` each time we want to browse its elements. + +`IEnumerator` is the "enumerator", that is the object that will be in charge of "Browsing" the elements to enumerate. + +So to make a class as enumerable, we implements `IEnumerable` which will create an `IEnumerator` which will be responsible of the iteration of the elements. + +Notice: all lists, collections, dictionnaries, and arrays in .Net, implement `IEnumerable`. + +## IEnumerable/IEnumerable<T> interface + +This interface indicates we can enumerate the object of the class that implements it. It provides only one method `GetEnumerator()` returning an `IEnumerator`. + +```csharp +public interface IEnumerable +{ + IEnumerator GetEnumerator(); +} +public interface IEnumerable : IEnumerable +{ + IEnumerator GetEnumerator(); +} +``` + +The purpose of the class is to provide a new enumerator object each time we need to start a new iteration, and permits to have several iterations in parallel on the same source (if the object logic permits it). + + +## IEnumerator/IEnumerator<T> interface + +This interface represents the iteration logic (enumerator object). This it wich indicates the current element and permits to "move" in the enumeration. + + +```csharp +public interface IEnumerator +{ + object Current { get; } + bool MoveNext(); + void Reset(); +} +public interface IEnumerator : IDisposable, IEnumerator +{ + T Current { get; } +} +``` + +The operation of the enumerator is simple: it indicates the current element of the enumeration (`Current` property), and moves to the next element with the `MoveNext()` method. + +## Iteration basics + +To navigate through an enumerable we use always the same principle: + +- Get a new enumerator from `IEnumerable` +- While `IEnumerator.MoveNext()` returns `true` + - Process `IEnumerator.Current` + +This is what the statement `foreach` do for us. + +So the next loop (`ForEach()` method in the sample program): + +```csharp +// Get the enumerable +IEnumerable enumerable = GetItems(); +// Iterates each element in 'enumerable' +foreach (int elm in enumerable) +{ + // .. +} +``` + +is approximately compiled like this (`IterationBase()` method in the sample program): + +```csharp +// Get the enumerable +IEnumerable enumerable = GetItems(); +// Get a new enumerator +IEnumerator enumerator = enumerable.GetEnumerator(); +// While the enumerator move +while (enumerator.MoveNext()) +{ + Int32 elm = enumerator.Current; + // .. +} +``` + +However this is not entirely exact, because the `foreach` loop handles the possibility than an enumerator be disposable (that implements `IDisposable`) (`ForEachDisposable()` method in the sample program show it), so in fact the the equivalent loop is more like this (`IterationBaseWithDispose()` method in the sample program) : + +```csharp +// Get the enumerable +IEnumerable enumerable = GetItems(true); +// Get a new enumerator +IEnumerator enumerator = enumerable.GetEnumerator(); +try +{ + // While the enumerator move + while (enumerator.MoveNext()) + { + Int32 elm = enumerator.Current; + // .. + Console.WriteLine(elm); + } +} +finally +{ + // Check if the enumerator is disposable + IDisposable disp = enumerator as IDisposable; + // If true the we dispose it + if (disp != null) + { + disp.Dispose(); + } +} +``` + +In this way that the loop terminates normally, or prematurely by a `break` statement, or by an exception, our enumerator will be disposed. + +It must be understood that implement 'IDisposable' on our enumerator is the only way we have to determine that an iteration is finished, especially if it has not browsed all the elements. + +Small aside: in fact `foreach` do not supports only the `IEnumerable`. What the compiler needs the `foreach` statement, is that the source to enumerate provides a `GetEnumerator()` public method that returns a type that provides a `MoveNext()` public method returning a boolean, and a `Current` property. In the sample program you can find the `ForEachWithoutIEnumerable()` method using the `FakeEnumerable` and `FakeEnumerator` objects that does not implement the interfaces. + +## The enumerators + +To make an enumerator we need to implements `IEnumerator`. However before you implement one, if you need iterate a private array (or another IEnumerable) for example, just return the array `GetNumerator()` method result. + +The implementation is simple, but when the enumerator is created, it is in en "undefined" state, it means that it is not yet moved, therefore `Current` must returns a default value, and we need wait the first `MoveNext()` to start the iteration. Which means that if you have some initializations to do (like connect to a daatabase for example) you do it when the first `MoveNext()` optimisation reasons; we may need to instanciate an enumerator without enumerate it, which it may be the case when you link several LINQ queries (see next part of the article). + +Suppose you want to create an enumerator to iterate a list in the reverse order. The `TestReverse()`method in the sample program show the use of our enumerator class. + +```csharp +/// +/// Enumerator iterates the list in the reverse way +/// +public class ReverseEnumerator : IEnumerator +{ + IList _Source; + int _Position; + bool _Completed; + + /// + /// Create a new enumerator + /// + public ReverseEnumerator(IList source) + { + this._Source = source; + // Set -1 to indicates the iteration is not started + this._Position = -1; + // The iteration is not finished + this._Completed = false; + // Set the Current value by default + this.Current = default(T); + } + + /// + /// Release the resources + /// + public void Dispose() + { + // Nothing to dispose, but mark the iterator as finished + this._Completed = true; + } + + /// + /// This method is called when we want to reset the enumerator + /// + public void Reset() + { + // Set -1 to indicates the iteration is not started + this._Position = -1; + // The iteration is not finished + this._Completed = false; + // Set the Current value by default + this.Current = default(T); + } + + /// + /// We go to the next element + /// + /// False when the iteration is finished + public bool MoveNext() + { + // If the source is null then we have nothing to browse, the iteration is finished + if (this._Source == null) return false; + + // If the iteration is finished, we stop here + if (this._Completed) return false; + + // If the is -1 we get the count of the elements to iterates for starting the iteration + if (this._Position == -1) + { + this._Position = _Source.Count; + } + + // We move on the list + this._Position--; + + // If we reach the -1 position the iteration is finished + if (this._Position < 0) + { + this._Completed = true; + return false; + } + + // We set Current and continue + Current = this._Source[this._Position]; + + return true; + } + + /// + /// Current element + /// + public T Current { get; private set; } + + /// + /// Current element for the non generic version + /// + object System.Collections.IEnumerator.Current + { + get { return Current; } + } + +} +``` + +As we can see, implement enumerator is simple. + +However it can quickly become complicated when we have complex enumerators (browse a syntaxic tree for example). We will see in next section how to simplify our code. + +Last words for this part: `IEnumerator` require to implements the `Reset()` method. You need to this method is not really used, most of the time it throws a `NotImplementedException`. So if your implementation is too complex, you can throw this exception too instead of. In fact, now we consider that if we need to restart an iteration, we call again `IEnumerable.GetEnumerator()` to create a new enumerator, so the `Reset()`is not more useful. + + +# The Yield methods + +Previously, we see how to implement an enumerator, but probably you think it could be complicated to create a class each time we need a special enumerator. Actually it can quickly end up with complex enumerators, with catching errors, release of multiple resources, etc., which can quickly complicate matters. + +It is there the C# compiler come to our rescue with the `yield` keyword (VB.NET also supports a `Yield` statement). + +For example we want an enumerable whereby we pass a list of files, and this enumerable iterate each file and read all the text lines of the file. + +This example is interesting because it included an error handling (a file may not exists, or be locked, etc.) and a multiple resources management (each file must be disposed). + +## Classic code version + +Let's start by a typically version code, like someone don't know the enumerable principle, or don't want to create an enumerator. + +This solution read all the files in a list, and when we want to enumerate them, we return the enumerator of the list (look the `TestFilesV1()` method in the sample program). + +```csharp +/// +/// Enumerable enumerates the text lines of a set of files +/// +public class EnumFilesV1 : IEnumerable +{ + private List _Lines; + /// + /// Create a new enumerable + /// + public EnumFilesV1(IEnumerable files) + { + // Init the files + this.Files = files.ToArray(); + // Mark the lines list as 'to load' by setting it to null + _Lines = null; + } + void LoadFiles() + { + // Create the lines list + _Lines = new List(); + if (this.Files != null) + { + // For each file + foreach (var file in Files) + { + try + { + // Open a file text reader + using (var reader = new StreamReader(file)) + { + // Read each line of the file + String line; + while ((line = reader.ReadLine()) != null) + { + // Add the line to the list + _Lines.Add(line); + } + } + } + catch { } // When an error raised while reading the file, go to the next + } + } + } + /// + /// Returns the lines enumerator + /// + public IEnumerator GetEnumerator() + { + // If the lines list is null then read the files + if (_Lines == null) + { + // Load the files + LoadFiles(); + } + // Returns the list enumerator + return _Lines.GetEnumerator(); + } + /// + /// Implements IEnumerator.GetEnumerator() (non generic version) + /// + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + /// + /// List of the files + /// + public String[] Files { get; private set; } +} +``` + +So what is wrong with this code ? + +First, if some files have content changing between to calls of `GetEnumerator()` we return only the initial read. We can resolve this by changing our code by rebuild the text lines list on each call. + +```csharp +/// +/// Enumerable enumerates the text lines of a set of files, rebuilding the list on each call +/// +public class EnumFilesV2 : IEnumerable +{ + /// + /// Create a new enumerable + /// + public EnumFilesV2(IEnumerable files) + { + // Init the files + this.Files = files.ToArray(); + } + IList LoadFiles() + { + // Create the lines list + var result = new List(); + if (this.Files != null) + { + // For each file + foreach (var file in Files) + { + try + { + // Open a file text reader + using (var reader = new StreamReader(file)) + { + // Read each line of the file + String line; + while ((line = reader.ReadLine()) != null) + { + // Add the line of the list + result.Add(line); + } + } + } + catch { } // When an error raised while reading the file, go to the next fichier + } + } + // Returns the list + return result; + } + /// + /// Returns the enumerator of the lines + /// + public IEnumerator GetEnumerator() + { + // Build the list + var list = LoadFiles(); + // Return the list enumerator + return list.GetEnumerator(); + } + /// + /// Implements IEnumerator.GetEnumerator() (non generic version) + /// + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + /// + /// List of the files + /// + public String[] Files { get; private set; } +} +``` + +We remove the list of text lines from the enumerable, and we build a new list on each call of `GetEnumerator()` and we return the enumerator of this new list (the `TestFilesV2()` method in the sample program shows the usage). + +Now we are in best respect of the enumerable principles, however is still not satisfy from a point of view performances. If we have some large files, we loading all lines in memory. If we have one million of lines and need to extract only the first thousand, we have loaded 999000 lines in memory for nothing :( + +## More optimized version + +The best solution would be to read a text line only when it's needed. So each time we wall `IEnumerator.MoveNext()`. We will read as streaming. + +```csharp +/// +/// Enumerable enumerates the text lines of a set of files via an enumerator +/// +public class EnumFilesV3 : IEnumerable +{ + /// + /// Create a new enumerable + /// + public EnumFilesV3(IEnumerable files) + { + // Init the files + this.Files = files.ToArray(); + } + /// + /// Returns the lines enumerator + /// + public IEnumerator GetEnumerator() + { + // Returns a new enumerator + return new FileEnumerator(Files); + } + /// + /// Implements IEnumerator.GetEnumerator() (non generic version) + /// + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + /// + /// List of the files + /// + public String[] Files { get; private set; } +} +/// +/// File enumerator +/// +class FileEnumerator : IEnumerator +{ + Func _CurrentState = null; + int _CurrentFilePos; + String _CurrentFileName; + TextReader _CurrentFile; + /// + /// Create a new enumerator + /// + public FileEnumerator(String[] files) + { + // Init the files + this.Files = files; + // Init the enumerator + Current = null; + _CurrentFilePos = 0; + _CurrentFileName = null; + _CurrentFile = null; + // The enumerator state is to open the next file to read + _CurrentState = OpenNextFileState; + } + /// + /// Dispose some resources + /// + public void Dispose() + { + // If we have a file opened we close it + if (_CurrentFile != null) + { + _CurrentFile.Dispose(); + _CurrentFile = null; + } + // Set the state to 'Completed' + _CurrentState = CompletedState; + } + /// + /// Try to open the next file in the list + /// + bool GetNextFile() + { + String filename = null; + TextReader file = null; + while (file == null && Files != null && _CurrentFilePos < Files.Length) + { + try + { + filename = Files[_CurrentFilePos++]; + file = new StreamReader(filename); + } + catch { } + } + // If we have a file opened + if (file != null) + { + _CurrentFileName = filename; + _CurrentFile = file; + return true; + } + // Else we don't found + return false; + } + /// + /// Open the next file + /// + bool OpenNextFileState() + { + // If we don't have file, we stop all + if (!GetNextFile()) + { + Current = null; + // Go to the state 'Completed' + _CurrentState = CompletedState; + // We finished + return false; + } + // Go to the state ReadNextLine + _CurrentState = ReadNextLineState; + // Read the first line + return _CurrentState(); + } + /// + /// Read line state + /// + bool ReadNextLineState() + { + try + { + // Read the next line in the file + String line = _CurrentFile.ReadLine(); + // If the line is not null we process it + if (line != null) + { + Current = line; + return true; + } + // The line is null so we reach the end of the file, we release the resource + } + catch + { + // If an error raised while reading we close the current file to avoid infinite loops + } + // Release resources to go to the next file + _CurrentFile.Dispose(); + _CurrentFile = null; + _CurrentFileName = null; + // Go to the state 'OpenNextFile' + _CurrentState = OpenNextFileState; + // Process the next state + return _CurrentState(); + } + /// + /// The iteration is finished, so we returns always false + /// + bool CompletedState() + { + return false; + } + /// + /// We don"t used this method + /// + public void Reset() + { + throw new NotSupportedException(); + } + /// + /// We move to the next line + /// + public bool MoveNext() + { + // Process the next state + return _CurrentState(); + } + /// + /// List of the files + /// + public String[] Files { get; private set; } + /// + /// Current value + /// + public string Current { get; private set; } + object System.Collections.IEnumerator.Current { get { return Current; } } +} +``` + +Now the reading part is defined in a enumerator. It a simple state machine: 'OpenNextFile', 'ReadNextFile' et 'Completed'. + +We can see we need to be vigilant on every location where an error can occur, and don't forget to release the resources according differents situations, etc. + +We read our files text line by text line, so our memory is on a minimal usage. In case of dispose we release the opened file, and we set the state to 'Completed' and the enumerator can't do nothing. + +We do lot of code for a little work, and for test it, you can look the `TestFilesV3()` method in the sample program: + +```csharp +private static void TestFilesV3() +{ + // Create the enumerable with tests files + var enumerable = new EnumFilesV3(GetTestFileNames()); + // Iterates the enumerable + foreach (var line in enumerable) + { + Console.WriteLine(line); + } + // Iterates the enumerable and force a break + int i = 0; + foreach (var line in enumerable) + { + if (i++ >= 4) break; + Console.WriteLine(line); + } +} +``` + +This example read all the lines in a first loop, and in a second loop it read only the first 4 lines. + +## 'yield' in our rescue + +So we have to situations: + +- either we create a small code, easily maintainable, but with some performances risks. +- either we create a more efficient code, but wich is more complex, long and sometimes difficult to maintain. + +And if we can reconcile the both ? + +For example: + +```csharp +/// +/// Open a new file or null if an error raised +/// +static StreamReader OpenFile(String file) +{ + try + { + return new StreamReader(file); + } + catch + { + return null; + } +} +static IEnumerable EnumFilesYield(IEnumerable files) +{ + if (files != null) + { + // For each files + foreach (var file in files) + { + // Open a file text reader + using (var reader = OpenFile(file)) + { + // reader can be null if an error raised + if (reader != null) + { + // Read each line of the file + String line; + do + { + // Read the line, if an error raised we stop the loop + try + { + line = reader.ReadLine(); + } + catch + { + break; + } + // Returns the line in the 'enumerable' + if (line != null) + yield return line; + } while (line != null);// Loop while we have a line + } + } + } + } +} +private static void TestFilesYield() +{ + // Get an enumerable with the files test + var enumerable = EnumFilesYield(GetTestFileNames()); + // Iterates the enumerable + foreach (var line in enumerable) + { + Console.WriteLine(line); + } + // Iterates the enumerable and force a break + int i = 0; + foreach (var line in enumerable) + { + if (i++ >= 4) break; + Console.WriteLine(line); + } +} +``` + +`TestFilesYield()` is a method which run two loops for a enumerable returns par the `EnumFilesYield()` method. + +It this last method that interest us, so little explanation of code. This method returns an `IEnumerable`, however it never returns an enumerable, instead of we have in the two loops a statement `yield return line;` where `line` is a `string`. + +The `yield return` in the method indicates to the compiler that method is an enumerator and each `yield return` returns an element of this enumerator. Technically the compiler will transforms this method in a `IEnumerator` object wich simulates the method code. + +In addition to the `yield return` there is a `yield break` that stop the enumerator. + +Overall the compiler can convert the most code to an enumerator, however we can't use `yield return` in a try-catch (but it's possible in a try-finally). And a `yield break` can be used in a try-catch but not in a try-finally. + +It's for that our code is more complex than a simple double loop to handle possible errors. But despite this there is still more short and easy to maintain than our previous enumerator. + +The generated enumerator supports `IDisposable`, for example in our case if the iteration is stopped when reading a file, and because there is a `using`, the compiler create the code dispose the resource in the `using`. Same as if in a `foreach` an exception is raised, and the `yield return` is within a `try-finally` this block will be executed. + +The `yield` keyword can be used in a method that returns an `IEnumerable` or an `IEnumerator`, permitting to implements the `IEnumerable.GetEnumerator()` with a `yield` method. + +Last point; the enumerator generated by the compiler don't implements the `IEnumerator.Reset()`, an `NotSupportedException` will be raised. + +For more informations about the `yield` and enumerable, look this links : + +- [yield : C# reference](https://msdn.microsoft.com/en-us/library/9k7k7cf0.aspx) +- [Iterators (C# and VB.Net)](https://msdn.microsoft.com/en-us/library/dscyy5s0.aspx) + +## The last word about 'yield' + +The keyword `yield` is very useful, it permits to handle some complex scenarios with a 'classic' code. The real difficulty is about the exceptions management, wich can complicate our code, but in general our code is always mor simple the create an enumerator. + +The Visual Studio compiler and debugger are very efficients by permitting to debug step-by-step within the generated enumerator by `yield`, and so tracing exactly what happens in the enumerator, event if the code in the yield method is complex, the trace follow exactly the code. + +The sample program provides the examples discussed in this part, and more other `yield` methods to show some complex things. + +# LINQ and the chaining enumerables + + + +~~~ TO TRANSLATE + + +LINQ est un language de requêtage intégré au language C# ou VB.Net, le propos de cette partie n'est pas d'expliquer LINQ en lui-même mais son comportement avec les énumérables dans la suite des deux précédentes parties. + +Pour plus d'informations sur LINQ je vous renvoie [à la documentation officielle](https://msdn.microsoft.com/fr-fr/library/bb397926.aspx). + +## Rappel + +Une requête LINQ ressemble à celà (méthode `TestLinqRequest()`) : + +```csharp +var query = from p in GetPersons() + where p.IsFamily + orderby p.Name + select p; +``` + +Cette requête n'est pas très compliquée : `GetPersons()` est une méthode qui retourne un `IEnumerable`, et notre requête en filtre les éléments dont le booléen `IsFamily` est à `true` et retourne le résultat trié par leur nom. + +Si on regarde de plus près la variable `query` (dans VisualStudio il suffit de survoler le mot clé **var** pour afficher une info-bulle informant du type déterminé par le compilateur) on constate qu'il s'agit d'un `IEnumerable`. + +En réalité il faut savoir que le compilateur va compiler cette requête sous la forme suivante (méthode `TestLinqRequestAsFluent()`) : + +```csharp +var query = GetPersons() + .Where(p => p.IsFamily) + .OrderBy(p => p.Name) + ; +``` + +LINQ fourni un ensemble de méthodes d'extensions (Select, Where, OrderBy, etc.) que l'on nomment "opérateur" car ils correspondent aux mot clés du language LINQ. + +Si on regarde de plus près les opérateurs de notre requête on constate qu'ils renvoient tous un `IEnumerable<>`. En fait chaque appel de ces opérateurs va créer un objet "proxy" de type énumérable qui encapsule l'énumérable "source". + +Ainsi notre variable `query` est en réalité une "chaîne" d'énumérables, elle contient un énumérable de type "OrderBy" qui se base sur un énumérable de type "Where" qui se base sur l'énumérable renvoyé par `GetPersons()`. + +## Mais alors, quel est le problème ? + +Voici une petite histoire vécue: un matin réception du mail suivant : + +> Je possède une table de données extrêmement volatile où je peux avoir une centaine de lignes insérées par seconde. +> +> Mon application affiche un tableau de bord avec des stats réactualisées régulièrement. Hors depuis que je suis en production LINQ calcul un nombre de lignes actualisées différents du nombre de lignes traitées, je ne comprends pas ! +> +> Voici mon code +> +> ```csharp +> class Stats { +> private DateTime lastRefresh; +> ... +> public void Refresh(){ +> var q = from stats in GetStats() +> where stat.Date >= lastRefresh +> orderby stat.Date +> select stats; +> +> foreach (var stats in q) +> { +> // Affichage de la stat +> } +> +> lblLastUpdate.Text = String.Format("{0} : {1} nouvelles statistiques", DateTime.Now, q.Count()); +> lastRefresh = DateTime.Now; +> } +> } +> ``` + +La source de données est un contexte Entity Framework, qui gère en réalité des `IQueryable` mais ces derniers sont des `IEnumerable`. + +Après discussion il s'avère que cette persone n'avait pas compris que sa requête `q` était un énumérable et que par conséquent chaque fois qu'il faisait une itération, un appel à GetEnumerator() était effectué et dans son cas une nouvelle requête SQL était exécutée. Hors l'opérateur `Count()` qu'il utilise pour déterminer le nombre de nouvelles statistiques ne renvoie pas un `IEnumerable` mais provoque une itération pour compter le nombre d'éléments. Seulement ses données étant tellement volatiles qu'entre les deux requêtes de nouvelles statistiques sont apparues. + +Le problème est là : en fait certaines personnes pensent qu'une requête LINQ est une sorte de liste tampon, qu'une fois qu'on a lancé une requête le résultat reste en mémoire, donc faire un `Count()` derrière se contente de compter le résultat. + +C'était la logique de cette personne, habituée à utiliser des principes de **Recordset** ou de **Dataset** pour les données, elle pensait qu'une requête LINQ était une sorte d'objet auto-alimenté contenant le résultat de sa requête. + +## Penser 'flux' + +Comme nous l'avons vu en début d'article, les énumérateurs sont une logique de "flux" ou on traite élément par élément. Dans le cadre d'une base de données chaque ligne étant retournée dés qu'elle est reçue du serveur. + +Nous avons vu également qu'un énumérable n'est qu'un fournisseur d'énumérateur, tant que l'on ne provoque pas l'énumération (appel à GetEnumerator()) nous n'avons pas de flux. + +Il a été également expliqué que LINQ était en fait une chaîne d'énumérable. Notre requête en début de cette partie pourrait ressembler à cela : + +```csharp +tatic IEnumerable SimulWhere() +{ + foreach (var p in GetPersons()) + { + if(p.IsFamily) + yield return p; + } +} +static IEnumerable SimulOrderBy() +{ + List result = new List(); + result.AddRange(SimulWhere()); + result.Sort((x, y) => String.Compare(x.Name, y.Name)); + foreach (var p in result) + yield return p; +} +static void TestSimulLinq() +{ + var query = SimulOrderBy(); + foreach (var p in query) + Console.WriteLine(p.Name); + foreach (var p in query.Take(2)) + Console.WriteLine(p.Name); +} +``` + +Dans la méthode `TestSimulLinq()` vous pouvez faire un pas à pas pour voir que tant qu'on n'entre pas dans un `foreach`, `SimulOrderBy()` n'est pas appelée. En revanche dans notre exemple elle est appelée deux fois car on a deux `foreach`. + +Donc il faut toujours penser nos requêtes en flux, qui se déclenchent à chaque fois que l'on provoque une itération. + +Alors une question se pose: comment savoir qu'une itération est déclenchée avec les opérateurs LINQ ? En bien c'est simple, il suffit de regarder le type de retour de l'opérateur, s'il renvoi un `IEnumerable` il ne provoque pas d'itération, mais renvoi un nouvel objet énumérable avec son propre énumérateur pour appliquer son opération sur le flux. + +Le langage LINQ intégré ne génère que des énumérables, par exemple `Count()` n'a pas d'équivalent LINQ, il faut utiliser la méthode, mais on peut coupler les deux : + +```csharp +int count = (from p in GetPersons() + where p.IsFamily + orderby p.Name + select p).Count(); +``` + +# Conclusion + +En espérant que cet article vous a apporté plus de clarté sur la logique des énumérables : + +- que `IEnumerable` ne contient pas de données, mais sert uniquement a créer un nouveau flux d'élément à chaque `GetEnumerator()` +- qu'une requête LINQ n'est pas un ensemble de données mais un énumérable +- qu'à chaque fois que vous avez un énumérable, il faut penser flux, quelque soit l'implémentation de cet énumérable + +# Voir Aussi + +- [Article d'origine](http://www.yeg-projects.com/2015/01/soyons-enumerables-partie-1/) +- [MSDN: Référence C# de yield](https://msdn.microsoft.com/fr-fr/library/9k7k7cf0.aspx) +- [MSDN: Les itérateurs en C# et VB.net](https://msdn.microsoft.com/fr-fr/library/dscyy5s0.aspx) +- [MSDN: Documentation officielle de LINQ](https://msdn.microsoft.com/fr-fr/library/bb397926.aspx) diff --git a/Enumerables-Technet/Article/article-fr.md b/Enumerables-Technet/Article/article-fr.md new file mode 100644 index 0000000..c488559 --- /dev/null +++ b/Enumerables-Technet/Article/article-fr.md @@ -0,0 +1,878 @@ +# Les Enumérables en détails + +Cet article traite en détail du fonctionnement des énumérables en C#. + +Cet article est basé sur une série d'articles disponibles [sur ce blog](http://www.yeg-projects.com/2015/01/soyons-enumerables-partie-1/) écrit par Yan Grenier. + +Tous les exemples de code se trouvent dans le projet "Enumerable-fr" de la solution. + +# Les énumérables + +Le principe des énumérables est de permettre de parcourir une liste d'éléments en récupérant élément par élément, a la manière d'un flux (on parle d'itération ou d'énumération). L'utilisation la plus courante de ce principe se fait avec le mot clé `foreach`. + +Dans .Net les énumérables fonctionnent via les interfaces `System.Collections.IEnumerable` et `System.Collections.IEnumerator`. Ces interfaces existent en version générique `System.Collections.Generic.IEnumerable<>` et `System.Collections.Generic.IEnumerator<>`. + +A partir du moment où une classe implémente l'interface `IEnumerable` alors elle devient énumérable. + +`IEnumerable` indique que l'on peut énumérer un objet et il a la charge de fournir un \ `IEnumerator` à chaque fois que l'on veut parcourir ses éléments. + +`IEnumerator` est "l'énumérateur", c'est à dire l'objet qui va se charger de "parcourir" les éléments à énumérer. + +Donc pour rendre une classe énumérable, on implémente `IEnumerable` qui va créer un `IEnumerator` qui aura la charge de l'itération des éléments. + +A savoir: toutes les listes, collections, dictionnaires, ainsi que les tableaux du .Net, implémentent `IEnumerable`. + +## Interface IEnumerable/IEnumerable<T> + +Cette interface indique qu'on peut énumérer l'objet de la classe qui l'implémente. Elle ne possède qu'une méthode `GetEnumerator()` retournant un `IEnumerator`. + +```csharp +public interface IEnumerable +{ + IEnumerator GetEnumerator(); +} +public interface IEnumerable : IEnumerable +{ + IEnumerator GetEnumerator(); +} +``` + +Le principe de cette interface et de fournir un nouvel objet énumérateur à chaque appel permettant ainsi de démarrer une nouvelle itération, et permettre également d'avoir plusieurs itérations en parallèle sur la même source (si la logique de l'objet sous-jacent le permet). + +## Interface IEnumerator/IEnumerator<T> + +Cette interface représente la logique d'itération (objet énumérateur). C'est elle qui indique l'élément en cours et qui permet de se "déplacer" dans l'énumération. + +```csharp +public interface IEnumerator +{ + object Current { get; } + bool MoveNext(); + void Reset(); +} +public interface IEnumerator : IDisposable, IEnumerator +{ + T Current { get; } +} +``` + +Le fonctionnement de l'énumérateur est assez simple : il indique l'élément en cours d'énumération (propriété `Current`), et se déplace vers le prochain élément avec la méthode `MoveNext()`. + +## Principe de l'itération + +Pour parcourir un énumérable on utilise toujours le même principe : + +- On demande à `IEnumerable` un nouvel énumérateur +- Tant que `IEnumerator.MoveNext()` retourne `true` + - On traite `IEnumerator.Current` + +C'est ce que fait l'instruction `foreach` pour nous. + +Ainsi la boucle suivante (méthode `ForEach()` dans le programme d'exemple) : + +```csharp +// Récupération de l'énumérable +IEnumerable enumerable = GetItems(); +// Parcours chaque élément dans 'enumerable' +foreach (int elm in enumerable) +{ + // .. +} +``` + +est approximativement compilée comme ceci (méthode `IterationBase()` dans le programme d'exemple) : + +```csharp +// Récupération de l'énumérable +IEnumerable enumerable = GetItems(); +// Récupère un nouvel énumérateur +IEnumerator enumerator = enumerable.GetEnumerator(); +// Tant que l'énumérateur se déplace +while (enumerator.MoveNext()) +{ + Int32 elm = enumerator.Current; + // .. +} +``` + +Toutefois ce n'est pas tout à fait exact, car la boucle `foreach` gère également la possibilité qu'un énumérateur soit disposable (implémentant `IDisposable`) (la méthode `ForEachDisposable()` le montre), donc la boucle équivalente est en réalité plus comme ceci (méthode `IterationBaseWithDispose()` dans le programme d'exemple) : + +```csharp +// Récupération de l'énumérable +IEnumerable enumerable = GetItems(true); +// Récupère un nouvel énumérateur +IEnumerator enumerator = enumerable.GetEnumerator(); +try +{ + // Tant que l'énumérateur se déplace + while (enumerator.MoveNext()) + { + Int32 elm = enumerator.Current; + // .. + Console.WriteLine(elm); + } +} +finally +{ + // On détermine si l'énumérateur est disposable + IDisposable disp = enumerator as IDisposable; + // Si c'est le cas on dispose l'énumérateur + if (disp != null) + { + disp.Dispose(); + } +} +``` + +De cette manière que la boucle s'arrête normalement, ou prématurément via un `break`, ou à cause d'une exception, notre énumérateur sera disposé. + +Il faut comprendre qu'implémenter `IDisposable` sur notre énumérateur est le seul moyen que nous avons pour déterminer qu'une itération est terminée, surtout si on a pas parcouru l'ensemble des éléments. + +Petit aparté : en réalité `foreach` ne prend pas en charge que des `IEnumerable`. Ce qui importe pour le compilateur lors d'un `foreach` c'est que la source à énumérer possède une méthode `GetEnumerator()` publique qui retourne un type qui possède une méthode `MoveNext()` retournant un booléen, et une propriété `Current`. Dans le programme d'exemple vous trouverez la méthode `ForEachWithoutIEnumerable()` utilisant les objets `FakeEnumerable` et `FakeEnumerator` n'implémentant pas les interfaces. + +## Les énumérateurs + +Pour faire un énumérateur il faut donc implémenter `IEnumerator`. Toutefois avant d'en implémenter un, si vous voulez énumérer un tableau privé (ou tout autre IEnumerable) par exemple, il vous suffit de renvoyer le `GetNumerator()` du tableau. + +L'implémentation est assez simple au final, ce qui importe c'est qu'à sa création l'énumérateur est dans un état "indéfini", c'est à dire qu'on ne s'est pas encore déplacé, donc `Current` doit se trouver avec une valeur par défaut, et on doit attendre le premier `MoveNext()` pour commencer vraiment notre itération. Ce qui implique que si vous avez des initialisations à faire (se connecter à une base de données par exemple) vous devez le faire lors du premier `MoveNext()` pour des raisons d'optimisation; on peut avoir besoin d'instancier un énumérateur sans pour autant le parcourir, ce qui peut être le cas quand on enchaîne plusieurs énumérables comme LINQ (cf prochaine partie de cet article). + +Imaginons que nous voulons créer un énumérateur qui parcours une liste à l'envers. La méthode `TestReverse()` du programme d'exemple montre l'utilisation de notre classe énumérateur. + +```csharp +/// +/// Enumérateur parcourant une liste dans le sens inverse +/// +public class ReverseEnumerator : IEnumerator +{ + IList _Source; + int _Position; + bool _Completed; + + /// + /// Création d'un nouvel énumérateur + /// + public ReverseEnumerator(IList source) + { + this._Source = source; + // On met -1 pour indiquer qu'on a pas commencé l'itération + this._Position = -1; + // L'itération n'est pas terminée + this._Completed = false; + // On défini Current avec la valeur par défaut + this.Current = default(T); + } + + /// + /// Libération des ressources + /// + public void Dispose() + { + // On a rien à libérer , mais on marque notre itérateur comme terminé + this._Completed = true; + } + + /// + /// Cette méthode est appelée lorsque l'on veut réinitialiser l'énumérateur + /// + public void Reset() + { + // On met -1 pour indiquer qu'on a pas commencer l'itération + this._Position = -1; + // L'itération n'est pas terminée + this._Completed = false; + // On défini Current avec la valeur par défaut + this.Current = default(T); + } + + /// + /// On se déplace vers le prochain élément + /// + /// False lorsque l'itération est terminée + public bool MoveNext() + { + // Si la source est Null alors on a rien à parcourir, donc l'itération s'arrête + if (this._Source == null) return false; + + // Si l'itération est terminée alors on ne va pas plus loin + if (this._Completed) return false; + + // Si la position est à -1 on récupère le nombre d'éléments à parcourir pour démarrer l'itération + if (this._Position == -1) + { + this._Position = _Source.Count; + } + + // On se déplace dans la liste + this._Position--; + + // Si on a atteind -1 alors on a terminé l'itération + if (this._Position < 0) + { + this._Completed = true; + return false; + } + + // On défini Current et on continue + Current = this._Source[this._Position]; + + return true; + } + + /// + /// Elément en cours de l'itération + /// + public T Current { get; private set; } + + /// + /// Elément en cours pour la version non générique + /// + object System.Collections.IEnumerator.Current + { + get { return Current; } + } + +} +``` + +Comme on peut le voir le principe de l'implémentation est assez simple. + +Toutefois ça peut vite se compliquer quand nous avons des énumérateurs complexes (parcours d'un arbre syntaxique par exemple). Nous verrons au chapitre suivant comment on peut se simplifier la vie. + +Dernier point pour cette partie : `IEnumerator` demande l'implémentation de `Reset()`. Il faut savoir que cette méthode n'est pas vraiment utilisée, la plupart du temps elle lève une exception `NotImplementedException`. Donc si son implémentation est trop complexe, n'ayez pas peur de faire de même. En fait on part du principe que si on veut redémarrer une itération on fait à nouveau appel à `IEnumerable.GetEnumerator()` qui va nous instancier un nouvel énumérateur, donc le reset n'a plus lieu d'être. + +# Les méthodes Yield + +Précédemment, certains d'entre vous ont dû se dire qu'à chaque fois que l'on veut mettre en place un énumérateur un peu particulier, il y a beaucoup de code à mettre place. + +Effectivement on peut vite se retrouver avec des énumérateurs complexes, avec interception d'erreurs, libération de ressources multiples, etc., ce qui peut rapidement compliquer les choses. + +C'est là que le compilateur C# va venir à notre rescousse via le mot clé yield (VB.NET supporte également une instruction `Yield`). + +Bien imaginons par exemple que nous voulons un énumérable auquel nous transmettons une liste de fichiers, et cet énumérable va parcourir chaque fichier et énumérer chaque ligne de texte du fichier. + +Cet exemple est intéressant car il inclus un gestion des erreurs (un fichier peut ne pas exister, ou être verrouillé, etc.) et une gestion des ressources multiples (chaque fichier doit être disposé). + +## Version brut de code + +Commençons par une version ressemblant typiquement à la solution que va prendre quelqu'un qui ne comprends pas les principes des énumérables ou tout simplement qui cherche à éviter la difficulté de faire un énumérateur correct. + +Cette solution se contente de lire tous les fichiers dans une liste, et lorsqu'on à besoin d'un énumérateur on retourne celui de la liste. (La méthode `TestFilesV1()` dans le programme exemple montre son utilisation). + +```csharp +/// +/// Enumérable parcourant les lignes texte d'un ensemble de fichier +/// +public class EnumFilesV1 : IEnumerable +{ + private List _Lines; + /// + /// Création d'un nouvel énumérable + /// + public EnumFilesV1(IEnumerable files) + { + // Initialisation des fichiers + this.Files = files.ToArray(); + // On marque la liste des lignes à charger en la mettant à null + _Lines = null; + } + void LoadFiles() + { + // Création de la liste des lignes + _Lines = new List(); + if (this.Files != null) + { + // Pour chaque fichier + foreach (var file in Files) + { + try + { + // Ouverture d'un lecteur de fichier texte + using (var reader = new StreamReader(file)) + { + // Lecture de chaque ligne du fichier + String line; + while ((line = reader.ReadLine()) != null) + { + // On ajoute la ligne dans la liste + _Lines.Add(line); + } + } + } + catch { } // Si une erreur à la lecture du fichier on passe au prochain fichier + } + } + } + /// + /// Retourne l'énumérateur des lignes + /// + public IEnumerator GetEnumerator() + { + // Si la liste des lignes est null alors il faut lire les fichiers + if (_Lines == null) + { + // Chargement des fichiers + LoadFiles(); + } + // Retourne l'énumérateur de la liste + return _Lines.GetEnumerator(); + } + /// + /// Implémentation de IEnumerator.GetEnumerator() (version non générique) + /// + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + /// + /// Liste des fichiers + /// + public String[] Files { get; private set; } +} +``` + +Alors qu'est-ce qui ne va pas avec ce code ? + +La première chose c'est que si les fichiers venaient à changer de contenu entre deux appels de `GetEnumerator()` nous retournerons à chaque fois le contenu de la lecture initiale. On peut résoudre ce problème simplement en modifiant légèrement notre code pour reconstruire la liste des lignes à chaque appel. + +```csharp +/// +/// Enumérable parcourant les lignes texte d'un ensemble de fichier reconstruisant une liste à chaque appel +/// +public class EnumFilesV2 : IEnumerable +{ + /// + /// Création d'un nouvel énumérable + /// + public EnumFilesV2(IEnumerable files) + { + // Initialisation des fichiers + this.Files = files.ToArray(); + } + IList LoadFiles() + { + // Création de la liste des lignes + var result = new List(); + if (this.Files != null) + { + // Pour chaque fichier + foreach (var file in Files) + { + try + { + // Ouverture d'un lecteur de fichier texte + using (var reader = new StreamReader(file)) + { + // Lecture de chaque ligne du fichier + String line; + while ((line = reader.ReadLine()) != null) + { + // On ajoute la ligne dans la liste + result.Add(line); + } + } + } + catch { } // Si une erreur à la lecture du fichier on passe au prochain fichier + } + } + // On retourne la liste + return result; + } + /// + /// Retourne l'énumérateur des lignes + /// + public IEnumerator GetEnumerator() + { + // On construit la liste + var list = LoadFiles(); + // Retourne l'énumérateur de la liste + return list.GetEnumerator(); + } + /// + /// Implémentation de IEnumerator.GetEnumerator() (version non générique) + /// + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + /// + /// Liste des fichiers + /// + public String[] Files { get; private set; } +} +``` + +On supprime la liste des lignes de l'énumérable et on construit une nouvelle liste à chaque appel de `GetEnumerator()` et on retourne l'énumerateur de cette nouvelle liste. (La méthode TestFilesV2() dans le programme exemple montre son utilisation). + +Cette fois nous respectons un peu mieux les principes des énumérables toutefois ce n'est toujours pas satisfaisant d'un point de vue des performances. En effet s'il s'avère que les fichiers sont volumineux, nous chargeons tout dans une liste en mémoire. Imaginons que nous nous servons de cet énumérable pour filtrer quelques lignes sur un million, nous pouvons engorger notre mémoire inutilement. Pire si on extrait que les milles premières lignes, nous aurons chargé 999000 lignes de trop :( + +## Version plus subtile + +L'idéal serait de ne lire une ligne de texte que quand on en a besoin. Bref en gros lors du `IEnumerator.MoveNext()`. + +Nous allons donc faire preuve de subtilité et gérer la lecture en flux. + +```csharp +/// +/// Enumérable parcourant les lignes texte d'un ensemble de fichier via un énumérateur +/// +public class EnumFilesV3 : IEnumerable +{ + /// + /// Création d'un nouvel énumérable + /// + public EnumFilesV3(IEnumerable files) + { + // Initialisation des fichiers + this.Files = files.ToArray(); + } + /// + /// Retourne l'énumérateur des lignes + /// + public IEnumerator GetEnumerator() + { + // Retourne un nouvel énumérateur + return new FileEnumerator(Files); + } + /// + /// Implémentation de IEnumerator.GetEnumerator() (version non générique) + /// + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + /// + /// Liste des fichiers + /// + public String[] Files { get; private set; } +} +/// +/// Enumérateur de fichier +/// +class FileEnumerator : IEnumerator +{ + Func _CurrentState = null; + int _CurrentFilePos; + String _CurrentFileName; + TextReader _CurrentFile; + /// + /// Création d'un nouvel énumérateur + /// + public FileEnumerator(String[] files) + { + // Initialisation des fichiers + this.Files = files; + // Initialisation de l'énumérateur + Current = null; + _CurrentFilePos = 0; + _CurrentFileName = null; + _CurrentFile = null; + // L'état de l'énumérateur est à l'ouverture du prochain fichier à traiter + _CurrentState = OpenNextFileState; + } + /// + /// Libération des ressources éventuelles + /// + public void Dispose() + { + // Si on a un fichier encore d'ouvert on libère la mémoire + if (_CurrentFile != null) + { + _CurrentFile.Dispose(); + _CurrentFile = null; + } + // On défini l'état 'Completed' + _CurrentState = CompletedState; + } + /// + /// Essayes d'ouvrir le prochain fichier de la liste + /// + bool GetNextFile() + { + String filename = null; + TextReader file = null; + while (file == null && Files != null && _CurrentFilePos < Files.Length) + { + try + { + filename = Files[_CurrentFilePos++]; + file = new StreamReader(filename); + } + catch { } + } + // Si on a un fichier d'ouvert + if (file != null) + { + _CurrentFileName = filename; + _CurrentFile = file; + return true; + } + // Sinon on a rien trouvé + return false; + } + /// + /// Ouverture du prochain fichier + /// + bool OpenNextFileState() + { + // Si on a pas ou plus de fichier on arrête tout + if (!GetNextFile()) + { + Current = null; + // On passe à l'état 'Completed' + _CurrentState = CompletedState; + // On termine + return false; + } + // On passe à l'état ReadNextLine + _CurrentState = ReadNextLineState; + // On lit la première ligne + return _CurrentState(); + } + /// + /// On est en cours de lecture + /// + bool ReadNextLineState() + { + try + { + // On lit la prochaine ligne du fichier + String line = _CurrentFile.ReadLine(); + // Si la ligne n'est pas null on la traite + if (line != null) + { + Current = line; + return true; + } + // La ligne est null alors on a atteint la fin du fichier, on libère sa ressource + } + catch + { + // Si une erreur survient à la lecture on ferme le fichier en cours pour éviter les boucles infinies + } + // Libération des ressources pour passer au fichier suivant + _CurrentFile.Dispose(); + _CurrentFile = null; + _CurrentFileName = null; + // On passe à l'état 'OpenNextFile' + _CurrentState = OpenNextFileState; + // On traite le prochain état + return _CurrentState(); + } + /// + /// L'itération est terminée on retourne toujours false + /// + bool CompletedState() + { + return false; + } + /// + /// On ne s'occupe pas de cette méthode + /// + public void Reset() + { + throw new NotSupportedException(); + } + /// + /// On se déplace + /// + public bool MoveNext() + { + // Exécution de l'état en cours + return _CurrentState(); + } + /// + /// Liste des fichiers + /// + public String[] Files { get; private set; } + /// + /// Valeur en cours + /// + public string Current { get; private set; } + object System.Collections.IEnumerator.Current { get { return Current; } } +} +``` + +Donc cette fois la partie lecture est défini dans un énumérateur. Il s'agit d'une simple machine à états : 'OpenNextFile', 'ReadNextFile' et 'Completed'. + +On constate qu'il faut être vigilant à chaque endroit où une erreur peut survenir, ne pas oublier de libérer les ressources en fonction de différentes situations, etc. + +En revanche nous lisons nos fichiers ligne par ligne, par conséquent la charge mémoire est à son minimum. En cas de dispose on libère le fichier ouvert, et on se place sur l'état 'Completed' pour que l'énumérateur ne puisse plus rien faire. + +Pour gérer tout ce petit monde on a pas mal de ligne de code. + +Et pour tester tout ca (méthode `TestFilesV3()`) on utilise le code suivant : + +```csharp +private static void TestFilesV3() +{ + // Création de l'énumérable avec les fichiers de tests + var enumerable = new EnumFilesV3(GetTestFileNames()); + // On parcours l'énumérable + foreach (var line in enumerable) + { + Console.WriteLine(line); + } + // On parcours l'énumérable et provoque un arrêt prématuré + int i = 0; + foreach (var line in enumerable) + { + if (i++ >= 4) break; + Console.WriteLine(line); + } +} +``` + +Qui va parcourir une fois toutes les lignes de tous les fichiers, et une seconde fois mais uniquement les 4 premières lignes de la liste. + +## 'yield' à notre secours + +Donc nous avons deux situations : + +- soit nous faisons un code assez court facilement maintenable, mais qui risque de nous poser des problèmes techniques. +- soit nous faisons un code plus performant, mais qui est plus complexe, long et difficile à maintenir. + + +Et si on pouvait concilier les deux ? + +Par exemple : + +```csharp +/// +/// Ouvre un nouveau fichier ou retourne null si une erreur à lieu +/// +static StreamReader OpenFile(String file) +{ + try + { + return new StreamReader(file); + } + catch + { + return null; + } +} +static IEnumerable EnumFilesYield(IEnumerable files) +{ + if (files != null) + { + // Pour chaque fichier + foreach (var file in files) + { + // Ouverture d'un lecteur de fichier texte + using (var reader = OpenFile(file)) + { + // reader peut être null si une erreur à eu lieu + if (reader != null) + { + // Lecture de chaque ligne du fichier + String line; + do + { + // Lecture d'une ligne, si une erreur à lieu on arrête la boucle + try + { + line = reader.ReadLine(); + } + catch + { + break; + } + // On envoi la ligne d'énumérable + if (line != null) + yield return line; + } while (line != null);// Boucle tant qu'on a une ligne + } + } + } + } +} +private static void TestFilesYield() +{ + // Récupération d'un énumérable avec les fichiers de tests + var enumerable = EnumFilesYield(GetTestFileNames()); + // On parcours l'énumérable + foreach (var line in enumerable) + { + Console.WriteLine(line); + } + // On parcours l'énumérable et provoque un arrêt prématuré + int i = 0; + foreach (var line in enumerable) + { + if (i++ >= 4) break; + Console.WriteLine(line); + } +} +``` + +`TestFilesYield()` est une méthode qui lance deux boucles d'un énumérable renvoyé par la méthode `EnumFilesYield()`. + +C'est cette dernière qui nous intéresse, alors petite explication de code. On constate que cette méthode retourne un `IEnumerable`, en revanche elle ne renvoie jamais d'énumérable, a la place on a dans la double boucle de lecture de fichier une instruction `yield return line;` ou `line` est une `string`. + +Le `yield return` dans une méthode indique au compilateur que cette méthode est en fait un énumérateur et que chaque `yield return` renvoi un élément de cet énumérateur. Techniquement le compilateur va transformer cette méthode en un objet `IEnumerator` qui va simuler le code de la méthode. + +En plus du `yield return` il existe le `yield break` qui arrête l'énumérateur. + +Globalement le compilateur est capable de convertir la plupart du code en énumérateur, toutefois on ne peut pas utiliser `yield return` dans un try-catch (mais on le peut dans un try-finally). En revanche un `yield break` peut se trouver dans un try-catch mais pas un try-finally. + +C'est pour ça que notre code est un peu plus compliqué qu'une simple double boucle pour prendre en compte d'éventuelles erreurs. Mais malgré celà il reste toujours plus court et facile à maintenir que notre énumérateur précédent. + +L'énumérateur généré supporte le `IDisposable` par exemple dans notre cas si l'itération se termine en cours de lecture d'un fichier, comme il y a un `using`, le compilateur se chargera de créer le code nécessaire pour disposer la ressource se trouvant dans le `using`. De même que si dans une boucle `foreach` une exception est levée, et que l'on a un bloc `finally`qui englobe le `yield return` en cours alors ce bloc `finally` sera exécuté. + +Le mot clé `yield` peut être utilisé dans une méthode qui retourne `IEnumerable` mais également `IEnumerator` ce qui nous permet d'implémenter `IEnumerable.GetEnumerator()` par une méthode `yield`. + +Dernier point, l'énumérateur généré par le compilateur ne prend pas en charge `IEnumerator.Reset()` une exception `NotSupportedException` est levée. + +Pour plus d'informations sur `yield` et les itérateurs, voici quelques liens : + +- [Référence C# de yield](https://msdn.microsoft.com/fr-fr/library/9k7k7cf0.aspx) +- [Les itérateurs en C# et VB.net](https://msdn.microsoft.com/fr-fr/library/dscyy5s0.aspx) + + +## Pour en finir avec 'yield' + +L'utilisation de `yield` est très pratique, elle permet de gérer des scénarios complexes avec un code 'classique'. La seule vraie difficulté réside dans la gestion des exceptions, qui peut compliquer notre code, mais de manière générale notre code reste toujours plus simple. + +Le compilateur et le debuggeur de Visual Studio sont extrêment performants et vous permettent de faire du pas à pas dans l'énumérateur généré par `yield`, et ainsi de tracer exactement ce qu'il se passe dans l'énumérateur, même si le code dans la méthode yield est complexe, la trace suit parfaitement votre code. + +Le programme d'exemple contient les différents exemples donnés, plus quelques méthodes `yield` suplémentaires pour montrer qu'on peut faire des choses complexes. + +# LINQ et les énumérables à la chaîne + +LINQ est un language de requêtage intégré au language C# ou VB.Net, le propos de cette partie n'est pas d'expliquer LINQ en lui-même mais son comportement avec les énumérables dans la suite des deux précédentes parties. + +Pour plus d'informations sur LINQ je vous renvoie [à la documentation officielle](https://msdn.microsoft.com/fr-fr/library/bb397926.aspx). + +## Rappel + +Une requête LINQ ressemble à celà (méthode `TestLinqRequest()`) : + +```csharp +var query = from p in GetPersons() + where p.IsFamily + orderby p.Name + select p; +``` + +Cette requête n'est pas très compliquée : `GetPersons()` est une méthode qui retourne un `IEnumerable`, et notre requête en filtre les éléments dont le booléen `IsFamily` est à `true` et retourne le résultat trié par leur nom. + +Si on regarde de plus près la variable `query` (dans VisualStudio il suffit de survoler le mot clé **var** pour afficher une info-bulle informant du type déterminé par le compilateur) on constate qu'il s'agit d'un `IEnumerable`. + +En réalité il faut savoir que le compilateur va compiler cette requête sous la forme suivante (méthode `TestLinqRequestAsFluent()`) : + +```csharp +var query = GetPersons() + .Where(p => p.IsFamily) + .OrderBy(p => p.Name) + ; +``` + +LINQ fourni un ensemble de méthodes d'extensions (Select, Where, OrderBy, etc.) que l'on nomment "opérateur" car ils correspondent aux mot clés du language LINQ. + +Si on regarde de plus près les opérateurs de notre requête on constate qu'ils renvoient tous un `IEnumerable<>`. En fait chaque appel de ces opérateurs va créer un objet "proxy" de type énumérable qui encapsule l'énumérable "source". + +Ainsi notre variable `query` est en réalité une "chaîne" d'énumérables, elle contient un énumérable de type "OrderBy" qui se base sur un énumérable de type "Where" qui se base sur l'énumérable renvoyé par `GetPersons()`. + +## Mais alors, quel est le problème ? + +Voici une petite histoire vécue: un matin réception du mail suivant : + +> Je possède une table de données extrêmement volatile où je peux avoir une centaine de lignes insérées par seconde. +> +> Mon application affiche un tableau de bord avec des stats réactualisées régulièrement. Hors depuis que je suis en production LINQ calcul un nombre de lignes actualisées différents du nombre de lignes traitées, je ne comprends pas ! +> +> Voici mon code +> +> ```csharp +> class Stats { +> private DateTime lastRefresh; +> ... +> public void Refresh(){ +> var q = from stats in GetStats() +> where stat.Date >= lastRefresh +> orderby stat.Date +> select stats; +> +> foreach (var stats in q) +> { +> // Affichage de la stat +> } +> +> lblLastUpdate.Text = String.Format("{0} : {1} nouvelles statistiques", DateTime.Now, q.Count()); +> lastRefresh = DateTime.Now; +> } +> } +> ``` + +La source de données est un contexte Entity Framework, qui gère en réalité des `IQueryable` mais ces derniers sont des `IEnumerable`. + +Après discussion il s'avère que cette persone n'avait pas compris que sa requête `q` était un énumérable et que par conséquent chaque fois qu'il faisait une itération, un appel à GetEnumerator() était effectué et dans son cas une nouvelle requête SQL était exécutée. Hors l'opérateur `Count()` qu'il utilise pour déterminer le nombre de nouvelles statistiques ne renvoie pas un `IEnumerable` mais provoque une itération pour compter le nombre d'éléments. Seulement ses données étant tellement volatiles qu'entre les deux requêtes de nouvelles statistiques sont apparues. + +Le problème est là : en fait certaines personnes pensent qu'une requête LINQ est une sorte de liste tampon, qu'une fois qu'on a lancé une requête le résultat reste en mémoire, donc faire un `Count()` derrière se contente de compter le résultat. + +C'était la logique de cette personne, habituée à utiliser des principes de **Recordset** ou de **Dataset** pour les données, elle pensait qu'une requête LINQ était une sorte d'objet auto-alimenté contenant le résultat de sa requête. + +## Penser 'flux' + +Comme nous l'avons vu en début d'article, les énumérateurs sont une logique de "flux" ou on traite élément par élément. Dans le cadre d'une base de données chaque ligne étant retournée dés qu'elle est reçue du serveur. + +Nous avons vu également qu'un énumérable n'est qu'un fournisseur d'énumérateur, tant que l'on ne provoque pas l'énumération (appel à GetEnumerator()) nous n'avons pas de flux. + +Il a été également expliqué que LINQ était en fait une chaîne d'énumérable. Notre requête en début de cette partie pourrait ressembler à cela : + +```csharp +tatic IEnumerable SimulWhere() +{ + foreach (var p in GetPersons()) + { + if(p.IsFamily) + yield return p; + } +} +static IEnumerable SimulOrderBy() +{ + List result = new List(); + result.AddRange(SimulWhere()); + result.Sort((x, y) => String.Compare(x.Name, y.Name)); + foreach (var p in result) + yield return p; +} +static void TestSimulLinq() +{ + var query = SimulOrderBy(); + foreach (var p in query) + Console.WriteLine(p.Name); + foreach (var p in query.Take(2)) + Console.WriteLine(p.Name); +} +``` + +Dans la méthode `TestSimulLinq()` vous pouvez faire un pas à pas pour voir que tant qu'on n'entre pas dans un `foreach`, `SimulOrderBy()` n'est pas appelée. En revanche dans notre exemple elle est appelée deux fois car on a deux `foreach`. + +Donc il faut toujours penser nos requêtes en flux, qui se déclenchent à chaque fois que l'on provoque une itération. + +Alors une question se pose: comment savoir qu'une itération est déclenchée avec les opérateurs LINQ ? En bien c'est simple, il suffit de regarder le type de retour de l'opérateur, s'il renvoi un `IEnumerable` il ne provoque pas d'itération, mais renvoi un nouvel objet énumérable avec son propre énumérateur pour appliquer son opération sur le flux. + +Le langage LINQ intégré ne génère que des énumérables, par exemple `Count()` n'a pas d'équivalent LINQ, il faut utiliser la méthode, mais on peut coupler les deux : + +```csharp +int count = (from p in GetPersons() + where p.IsFamily + orderby p.Name + select p).Count(); +``` + +# Conclusion + +En espérant que cet article vous a apporté plus de clarté sur la logique des énumérables : + +- que `IEnumerable` ne contient pas de données, mais sert uniquement a créer un nouveau flux d'élément à chaque `GetEnumerator()` +- qu'une requête LINQ n'est pas un ensemble de données mais un énumérable +- qu'à chaque fois que vous avez un énumérable, il faut penser flux, quelque soit l'implémentation de cet énumérable + +# Voir Aussi + +- [Article d'origine](http://www.yeg-projects.com/2015/01/soyons-enumerables-partie-1/) +- [MSDN: Référence C# de yield](https://msdn.microsoft.com/fr-fr/library/9k7k7cf0.aspx) +- [MSDN: Les itérateurs en C# et VB.net](https://msdn.microsoft.com/fr-fr/library/dscyy5s0.aspx) +- [MSDN: Documentation officielle de LINQ](https://msdn.microsoft.com/fr-fr/library/bb397926.aspx) diff --git a/Enumerables-Technet/README.md b/Enumerables-Technet/README.md new file mode 100644 index 0000000..592396a --- /dev/null +++ b/Enumerables-Technet/README.md @@ -0,0 +1,6 @@ +Enumérables - Version Technet +============================= + +Ce projet est une refonte des articles sur les énumérables pour une version Wiki Technet. + +This project is a rewrite of my articles about enumerables for a Wiki Technet version. diff --git a/Enumerables-Technet/Source/Enumerables-en/App.config b/Enumerables-Technet/Source/Enumerables-en/App.config new file mode 100644 index 0000000..9c05822 --- /dev/null +++ b/Enumerables-Technet/Source/Enumerables-en/App.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/Enumerables-Technet/Source/Enumerables-en/EnumFilesV1.cs b/Enumerables-Technet/Source/Enumerables-en/EnumFilesV1.cs new file mode 100644 index 0000000..970543f --- /dev/null +++ b/Enumerables-Technet/Source/Enumerables-en/EnumFilesV1.cs @@ -0,0 +1,90 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Enumerables +{ + /// + /// Enumerable enumerates the text lines of a set of files + /// + public class EnumFilesV1 : IEnumerable + { + + private List _Lines; + + /// + /// Create a new enumerable + /// + public EnumFilesV1(IEnumerable files) + { + // Init the files + this.Files = files.ToArray(); + + // Mark the lines list as 'to load' by setting it to null + _Lines = null; + } + + void LoadFiles() + { + // Create the lines list + _Lines = new List(); + + if (this.Files != null) + { + // For each file + foreach (var file in Files) + { + try + { + // Open a file text reader + using (var reader = new StreamReader(file)) + { + // Read each line of the file + String line; + while ((line = reader.ReadLine()) != null) + { + // Add the line to the list + _Lines.Add(line); + } + } + } + catch { } // When an error raised while reading the file, go to the next + } + } + } + + /// + /// Returns the lines enumerator + /// + public IEnumerator GetEnumerator() + { + // If the lines list is null then read the files + if (_Lines == null) + { + // Load the files + LoadFiles(); + } + + // Returns the list enumerator + return _Lines.GetEnumerator(); + } + + /// + /// Implements IEnumerator.GetEnumerator() (non generic version) + /// + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + /// + /// List of the files + /// + public String[] Files { get; private set; } + + } + +} diff --git a/Enumerables-Technet/Source/Enumerables-en/EnumFilesV2.cs b/Enumerables-Technet/Source/Enumerables-en/EnumFilesV2.cs new file mode 100644 index 0000000..94bd31a --- /dev/null +++ b/Enumerables-Technet/Source/Enumerables-en/EnumFilesV2.cs @@ -0,0 +1,84 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Enumerables +{ + /// + /// Enumerable enumerates the text lines of a set of files, rebuilding the list on each call + /// + public class EnumFilesV2 : IEnumerable + { + + /// + /// Create a new enumerable + /// + public EnumFilesV2(IEnumerable files) + { + // Init the files + this.Files = files.ToArray(); + } + + IList LoadFiles() + { + // Create the lines list + var result = new List(); + + if (this.Files != null) + { + // For each file + foreach (var file in Files) + { + try + { + // Open a file text reader + using (var reader = new StreamReader(file)) + { + // Read each line of the file + String line; + while ((line = reader.ReadLine()) != null) + { + // Add the line of the list + result.Add(line); + } + } + } + catch { } // When an error raised while reading the file, go to the next + } + } + + // Returns the list + return result; + } + + /// + /// Returns the enumerator of the lines + /// + public IEnumerator GetEnumerator() + { + // Build the list + var list = LoadFiles(); + + // Return the list enumerator + return list.GetEnumerator(); + } + + /// + /// Implements IEnumerator.GetEnumerator() (non generic version) + /// + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + /// + /// List of the files + /// + public String[] Files { get; private set; } + + } + +} diff --git a/Enumerables-Technet/Source/Enumerables-en/EnumFilesV3.cs b/Enumerables-Technet/Source/Enumerables-en/EnumFilesV3.cs new file mode 100644 index 0000000..d8a6e49 --- /dev/null +++ b/Enumerables-Technet/Source/Enumerables-en/EnumFilesV3.cs @@ -0,0 +1,209 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Enumerables +{ + /// + /// Enumerable enumerates the text lines of a set of files via an enumerator + /// + public class EnumFilesV3 : IEnumerable + { + + /// + /// Create a new enumerable + /// + public EnumFilesV3(IEnumerable files) + { + // Init the files + this.Files = files.ToArray(); + } + + /// + /// Returns the lines enumerator + /// + public IEnumerator GetEnumerator() + { + // Returns a new enumerator + return new FileEnumerator(Files); + } + + /// + /// Implements IEnumerator.GetEnumerator() (non generic version) + /// + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + /// + /// List of the files + /// + public String[] Files { get; private set; } + + } + + /// + /// File enumerator + /// + class FileEnumerator : IEnumerator + { + Func _CurrentState = null; + int _CurrentFilePos; + String _CurrentFileName; + TextReader _CurrentFile; + + /// + /// Create a new enumerator + /// + public FileEnumerator(String[] files) + { + // Init the files + this.Files = files; + // Init the enumerator + Current = null; + _CurrentFilePos = 0; + _CurrentFileName = null; + _CurrentFile = null; + // The enumerator state is to open the next file to read + _CurrentState = OpenNextFileState; + } + + /// + /// Dispose some resources + /// + public void Dispose() + { + // If we have a file opened we close it + if (_CurrentFile != null) + { + _CurrentFile.Dispose(); + _CurrentFile = null; + } + // Set the state to 'Completed' + _CurrentState = CompletedState; + } + + /// + /// Try to open the next file in the list + /// + bool GetNextFile() + { + String filename = null; + TextReader file = null; + while (file == null && Files != null && _CurrentFilePos < Files.Length) + { + try + { + filename = Files[_CurrentFilePos++]; + file = new StreamReader(filename); + } + catch { } + } + // If we have a file opened + if (file != null) + { + _CurrentFileName = filename; + _CurrentFile = file; + return true; + } + // Else we don't found + return false; + } + + /// + /// Open the next file + /// + bool OpenNextFileState() + { + // If we don't have file, we stop all + if (!GetNextFile()) + { + Current = null; + // Go to the state 'Completed' + _CurrentState = CompletedState; + // We finished + return false; + } + + // Go to the state ReadNextLine + _CurrentState = ReadNextLineState; + + // Read the first line + return _CurrentState(); + } + + /// + /// Read line state + /// + bool ReadNextLineState() + { + try + { + // Read the next line in the file + String line = _CurrentFile.ReadLine(); + // If the line is not null we process it + if (line != null) + { + Current = line; + return true; + } + // The line is null so we reach the end of the file, we release the resource + } + catch + { + // If an error raised while reading we close the current file to avoid infinite loops + + } + // Release resources to go to the next file + _CurrentFile.Dispose(); + _CurrentFile = null; + _CurrentFileName = null; + // Go to the state 'OpenNextFile' + _CurrentState = OpenNextFileState; + // Process the next state + return _CurrentState(); + } + + /// + /// The iteration is finished, so we returns always false + /// + bool CompletedState() + { + return false; + } + + /// + /// We don"t used this method + /// + public void Reset() + { + throw new NotSupportedException(); + } + + /// + /// We move to the next line + /// + public bool MoveNext() + { + // Process the next state + return _CurrentState(); + } + + /// + /// List of the files + /// + public String[] Files { get; private set; } + + /// + /// Current value + /// + public string Current { get; private set; } + object System.Collections.IEnumerator.Current { get { return Current; } } + + } + +} diff --git a/Enumerables-Technet/Source/Enumerables-en/EnumerableWithEnumeratorDisposable.cs b/Enumerables-Technet/Source/Enumerables-en/EnumerableWithEnumeratorDisposable.cs new file mode 100644 index 0000000..b6f25d0 --- /dev/null +++ b/Enumerables-Technet/Source/Enumerables-en/EnumerableWithEnumeratorDisposable.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Enumerables +{ + /// + /// Enumerable class using disposable enumerators + /// + class EnumerableWithEnumeratorDisposable : IEnumerable + { + List _Source; + + /// + /// Create a new enumerable + /// + /// Data source + /// Action invoked when an enumerator was disposed + public EnumerableWithEnumeratorDisposable(IEnumerable source, Action onDispose = null) + { + _Source = new List(source); + OnDispose = onDispose; + } + + /// + /// Return a disposable enumerator + /// + public IEnumerator GetEnumerator() + { + return new EnumeratorDisposable(_Source, OnDispose); + } + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + /// + /// Action invoked when an enumerator was disposed + /// + public Action OnDispose { get; set; } + + } + +} diff --git a/Enumerables-Technet/Source/Enumerables-en/Enumerables-en.csproj b/Enumerables-Technet/Source/Enumerables-en/Enumerables-en.csproj new file mode 100644 index 0000000..eea64fc --- /dev/null +++ b/Enumerables-Technet/Source/Enumerables-en/Enumerables-en.csproj @@ -0,0 +1,80 @@ + + + + + Debug + AnyCPU + {6373827A-FF33-4A4F-A7F7-934903CD17EA} + Exe + Properties + Enumerables + Enumerables + v4.5.1 + 512 + true + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Always + + + Always + + + Always + + + + + \ No newline at end of file diff --git a/Enumerables-Technet/Source/Enumerables-en/EnumeratorDisposable.cs b/Enumerables-Technet/Source/Enumerables-en/EnumeratorDisposable.cs new file mode 100644 index 0000000..917fcfe --- /dev/null +++ b/Enumerables-Technet/Source/Enumerables-en/EnumeratorDisposable.cs @@ -0,0 +1,61 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Enumerables +{ + /// + /// Disposable enumerator + /// + class EnumeratorDisposable : IEnumerator, IDisposable + { + List _Source; + int _Current = -1; + + /// + /// Create a new enumerator + /// + /// Data source to enumerate + /// Action invoked when disposed + public EnumeratorDisposable(IEnumerable source, Action onDispose) + { + this._Source = new List(source); + this.OnDispose = onDispose; + } + /// + /// Dispose + /// + public void Dispose() + { + if (OnDispose != null) + OnDispose(); + } + /// + /// Current item + /// + public T Current { get; private set; } + object System.Collections.IEnumerator.Current { get { return Current; } } + /// + /// Move to the next item + /// + public bool MoveNext() + { + if (++_Current >= _Source.Count) return false; + Current = _Source[_Current]; + return true; + } + + public void Reset() + { + throw new NotImplementedException(); + } + + /// + /// Method invoked when the enumerator was disposed + /// + public Action OnDispose { get; set; } + } + +} diff --git a/Enumerables-Technet/Source/Enumerables-en/FakeEnumerable.cs b/Enumerables-Technet/Source/Enumerables-en/FakeEnumerable.cs new file mode 100644 index 0000000..ced2d06 --- /dev/null +++ b/Enumerables-Technet/Source/Enumerables-en/FakeEnumerable.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Enumerables +{ + /// + /// Fake enumerable that don't implements IEnumerable but supported by 'foreach' + /// + class FakeEnumerable + { + /// + /// foreach require the class have only a public method GetEnumerator() returning an object with a public method + /// MoveNext() and e public property Current. + /// + public FakeEnumerator GetEnumerator() + { + return new FakeEnumerator(); + } + } + +} diff --git a/Enumerables-Technet/Source/Enumerables-en/FakeEnumerator.cs b/Enumerables-Technet/Source/Enumerables-en/FakeEnumerator.cs new file mode 100644 index 0000000..7a27fe6 --- /dev/null +++ b/Enumerables-Technet/Source/Enumerables-en/FakeEnumerator.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Enumerables +{ + /// + /// Fake enumerator that don't implements IEnumerator but supported by 'foreach' + /// + /// + /// Need to implements a public method MoveNext() and a public property Current + /// + class FakeEnumerator + { + List _Source = new List(new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }); + int _Current = -1; + /// + /// Current item + /// + public int Current { get; private set; } + /// + /// Move to the next item + /// + public bool MoveNext() + { + if (++_Current >= _Source.Count) return false; + Current = _Source[_Current]; + return true; + } + } +} diff --git a/Enumerables-Technet/Source/Enumerables-en/Files/Fichier1.txt b/Enumerables-Technet/Source/Enumerables-en/Files/Fichier1.txt new file mode 100644 index 0000000..0f2a04e --- /dev/null +++ b/Enumerables-Technet/Source/Enumerables-en/Files/Fichier1.txt @@ -0,0 +1,3 @@ +First line of the first file. +Second line of the first file. +Third line of the first file. diff --git a/Enumerables-Technet/Source/Enumerables-en/Files/Fichier2.txt b/Enumerables-Technet/Source/Enumerables-en/Files/Fichier2.txt new file mode 100644 index 0000000..be86a36 --- /dev/null +++ b/Enumerables-Technet/Source/Enumerables-en/Files/Fichier2.txt @@ -0,0 +1,3 @@ +First line of the second file. +Second line of the second file. +Third line of the second file. diff --git a/Enumerables-Technet/Source/Enumerables-en/Files/Fichier3.txt b/Enumerables-Technet/Source/Enumerables-en/Files/Fichier3.txt new file mode 100644 index 0000000..3597ed8 --- /dev/null +++ b/Enumerables-Technet/Source/Enumerables-en/Files/Fichier3.txt @@ -0,0 +1,3 @@ +First line of the third file. +Second line of the third file. +Third line of the third file. diff --git a/Enumerables-Technet/Source/Enumerables-en/Person.cs b/Enumerables-Technet/Source/Enumerables-en/Person.cs new file mode 100644 index 0000000..a5446b1 --- /dev/null +++ b/Enumerables-Technet/Source/Enumerables-en/Person.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Enumerables +{ + class Person + { + public String Name { get; set; } + public DateTime? BirthDay { get; set; } + public bool IsFamily { get; set; } + } +} diff --git a/Enumerables-Technet/Source/Enumerables-en/Program.cs b/Enumerables-Technet/Source/Enumerables-en/Program.cs new file mode 100644 index 0000000..add1690 --- /dev/null +++ b/Enumerables-Technet/Source/Enumerables-en/Program.cs @@ -0,0 +1,586 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Enumerables +{ + class Program + { + + /// + /// Returns a list of elements as enumerable + /// + /// Indicates is we returns an enumerable with disposable enumerator + static IEnumerable GetItems(bool asDisposable = false) + { + var items = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; + return asDisposable ? new EnumerableWithEnumeratorDisposable(items, () => Console.WriteLine("Enumerator disposed")).AsEnumerable() : items; + } + + /// + /// List of the soure files + /// + static IEnumerable GetTestFileNames() + { + var dir = AppDomain.CurrentDomain.BaseDirectory; + return new String[]{ + Path.Combine(dir, "Files", "Fichier1.txt"), + Path.Combine(dir, "Files", "Fichier2.txt"), + Path.Combine(dir, "Files", "Fichier3.txt") + }; + } + + /// + /// List of persons + /// + static IEnumerable GetPersons() + { + yield return new Person() { + Name = "Toto", + BirthDay = null, + IsFamily = true + }; + yield return new Person() { + Name = "Titi", + BirthDay = new DateTime(2010, 11, 27), + IsFamily = false + }; + yield return new Person() { + Name = "Tata", + BirthDay = null, + IsFamily = false + }; + yield return new Person() { + Name = "Tutu", + BirthDay = new DateTime(1994, 8, 11), + IsFamily = true + }; + } + + #region Loop samples + + /// + /// foreach sample + /// + static void ForEach() + { + // Get the enumerable + IEnumerable enumerable = GetItems(); + + // Iterates each element in 'enumerable' + foreach (int elm in enumerable) + { + // .. + Console.WriteLine(elm); + } + } + + /// + /// foreach sample with disposable enumerator + /// + static void ForEachDisposable() + { + // Get the enumerable + IEnumerable enumerable = GetItems(true); + + // Iterates each element in 'enumerable' + foreach (int elm in enumerable) + { + // .. + Console.WriteLine(elm); + } + } + + /// + /// Basic iteration sample + /// + static void IterationBase() + { + // Get the enumerable + IEnumerable enumerable = GetItems(); + + // Get a new enumerator + IEnumerator enumerator = enumerable.GetEnumerator(); + // While the enumerator move + while (enumerator.MoveNext()) + { + Int32 elm = enumerator.Current; + // .. + Console.WriteLine(elm); + } + } + + /// + /// Iteration sample with IDiposable management like 'foreach' do + /// + static void IterationBaseWithDispose() + { + // Get the enumerable + IEnumerable enumerable = GetItems(true); + + // Get a new enumerator + IEnumerator enumerator = enumerable.GetEnumerator(); + try + { + // While the enumerator move + while (enumerator.MoveNext()) + { + Int32 elm = enumerator.Current; + // .. + Console.WriteLine(elm); + } + } + finally + { + // Check if the enumerator is disposable + IDisposable disp = enumerator as IDisposable; + // If true the we dispose it + if (disp != null) + { + disp.Dispose(); + } + } + } + + /// + /// foreach sample with an object not implementing IEnumerable/IEnumerator + /// + static void ForEachWithoutIEnumerable() + { + // Get the enumerable + var enumerable = new FakeEnumerable(); + + // Iterates each element in 'enumerable' + foreach (int elm in enumerable) + { + // .. + Console.WriteLine(elm); + } + } + + /// + /// Test ReverseEnumerator + /// + static void TestReverse() + { + // ReverseEnumerator require an IList<> + IList list = new List(GetItems()); + + // Create the reverse enumerable + IEnumerable enumerable = new ReverseEnumerable(list); + + // Iterates each element in 'enumerable' + foreach (int elm in enumerable) + { + // .. + Console.WriteLine(elm); + } + + // Iterates each element and stop the loop when find the '5' element + foreach (int elm in enumerable) + { + if (elm == 5) break; + // .. + Console.WriteLine(elm); + } + + } + + #endregion + + #region Complex enumerators samples + + /// + /// Test of files enumerator first version + /// + static void TestFilesV1() + { + // Create the enumerable with tests files + var enumerable = new EnumFilesV1(GetTestFileNames()); + + // Iterates the enumerable + foreach (var line in enumerable) + { + Console.WriteLine(line); + } + + } + + /// + /// Test the files enumerator better than the first version + /// + static void TestFilesV2() + { + // Create the enumerable with tests files + var enumerable = new EnumFilesV2(GetTestFileNames()); + + // Iterates the enumerable + foreach (var line in enumerable) + { + Console.WriteLine(line); + } + + } + + /// + /// Test the files enumerator more better + /// + static void TestFilesV3() + { + // Create the enumerable with tests files + var enumerable = new EnumFilesV3(GetTestFileNames()); + + // Iterates the enumerable + foreach (var line in enumerable) + { + Console.WriteLine(line); + } + + // Iterates the enumerable and force a break + int i = 0; + foreach (var line in enumerable) + { + if (i++ >= 4) break; + Console.WriteLine(line); + } + } + + #endregion + + #region Yield method sample + + /// + /// Open a new file or null if an error raised + /// + static StreamReader OpenFile(String file) + { + try + { + return new StreamReader(file); + } + catch + { + return null; + } + } + /// + /// Enumerates the file with a Yield method + /// + static IEnumerable EnumFilesYield(IEnumerable files) + { + if (files != null) + { + // For each files + foreach (var file in files) + { + // Open a file text reader + using (var reader = OpenFile(file)) + { + // reader can be null if an error raised + if (reader != null) + { + // Read each line of the file + String line; + do + { + // Read the line, if an error raised we stop the loop + try + { + line = reader.ReadLine(); + } + catch + { + break; + } + // Returns the line in the 'enumerable' + if (line != null) + yield return line; + } while (line != null);// Loop while we have a line + } + } + } + } + } + /// + /// Test the Yield iteration + /// + static void TestFilesYield() + { + // Get an enumerable with the files test + var enumerable = EnumFilesYield(GetTestFileNames()); + + // Iterates the enumerable + foreach (var line in enumerable) + { + Console.WriteLine(line); + } + + // Iterates the enumerable and force a break + int i = 0; + foreach (var line in enumerable) + { + if (i++ >= 4) break; + Console.WriteLine(line); + } + + } + /// + /// Same as but without the exceptions management + /// + static IEnumerable EnumFilesYieldUnprotected(IEnumerable files) + { + if (files != null) + { + // For each file + foreach (var file in files) + { + // Open a file text reader + using (var reader = new StreamReader(file)) + { + // Read each line of the file + String line; + while ((line = reader.ReadLine()) != null) + { + // Returns the line in the enumerable + yield return line; + } + } + } + } + } + + /// + /// Complex Yield method + /// + static IEnumerable YieldComplex() + { + Random rnd = new Random(); + + // Direct emit + yield return "Start"; + + // Emit in a switch + for (int i = 0; i < 10; i++) + { + switch (rnd.Next(4)) + { + case 1: + yield return "Funky 1"; + break; + case 2: + continue; + case 3: + yield return "<<<<"; + // Emit an another enumerable + foreach (var line in EnumFilesYield(GetTestFileNames())) + { + // Set a condition + if (line.Contains("x")) + yield return line; + } + yield return ">>>>"; + break; + case 0: + default: + yield return "Funky 0"; + break; + } + } + + // Direct emit + yield return "End"; + } + + /// + /// Tesst the complex Yield method + /// + static void TestYieldComplex() + { + foreach (var item in YieldComplex()) + Console.WriteLine(item); + } + + #endregion + + #region LINQ samples + + /// + /// Simple LINQ request + /// + static void TestLinqRequest() + { + var query = from p in GetPersons() + where p.IsFamily + orderby p.Name + select p; + + foreach (var p in query) + Console.WriteLine(p.Name); + + foreach (var p in query.Take(2)) + Console.WriteLine(p.Name); + } + + /// + /// Simple LINQ request as fluent mode + /// + static void TestLinqRequestAsFluent() + { + var query = GetPersons() + .Where(p => p.IsFamily) + .OrderBy(p => p.Name) + ; + + foreach (var p in query) + Console.WriteLine(p.Name); + + foreach (var p in query.Take(2)) + Console.WriteLine(p.Name); + } + + //static void TestStats() + //{ + // var q = from stats in GetStats() + // where stat.Date >= lastRefresh + // orderby stat.Date + // select stats; + + // foreach (var stats in q) + // { + // // Display the stats + // } + + // lblLastUpdate.Text = String.Format("{0} : {1} new statistics", DateTime.Now, q.Count()); + // lastRefresh = DateTime.Now; + + //} + + /// + /// Where operator simulation + /// + static IEnumerable SimulWhere() + { + foreach (var p in GetPersons()) + { + if (p.IsFamily) + yield return p; + } + } + + /// + /// OrderBy operator simulation + /// + static IEnumerable SimulOrderBy() + { + List result = new List(); + result.AddRange(SimulWhere()); + result.Sort((x, y) => String.Compare(x.Name, y.Name)); + foreach (var p in result) + yield return p; + } + + /// + /// LINQ simulation + /// + static void TestSimulLinq() + { + var query = SimulOrderBy(); + + foreach (var p in query) + Console.WriteLine(p.Name); + + foreach (var p in query.Take(2)) + Console.WriteLine(p.Name); + } + + #endregion + + static void WaitAndPress() + { + Console.WriteLine("Press a key to continue..."); + Console.ReadKey(); + Console.WriteLine(); + } + static void Main(string[] args) + { + #region Loop samples + Console.WriteLine("* ForEach loop"); + ForEach(); + Console.WriteLine(); + WaitAndPress(); + + Console.WriteLine("* ForEach loop whith an IDisposable"); + ForEachDisposable(); + Console.WriteLine(); + WaitAndPress(); + + Console.WriteLine("* Basic iteration (ForEach simulation)"); + IterationBase(); + Console.WriteLine(); + WaitAndPress(); + + Console.WriteLine("* Basic iteration with a disposable (ForEach simulation)"); + IterationBaseWithDispose(); + Console.WriteLine(); + WaitAndPress(); + + Console.WriteLine("* ForEach loop with a non IEnumerable object"); + ForEachWithoutIEnumerable(); + Console.WriteLine(); + WaitAndPress(); + + Console.WriteLine("* Test ReverseEnumerator"); + TestReverse(); + Console.WriteLine(); + WaitAndPress(); + #endregion + + #region Complex enumerators and yield method samples + Console.WriteLine("* Files enumerator V1 (basic way)"); + TestFilesV1(); + Console.WriteLine(); + WaitAndPress(); + + Console.WriteLine("* Files enumerator V2 (better version than the first version)"); + TestFilesV2(); + Console.WriteLine(); + WaitAndPress(); + + Console.WriteLine("* Files enumerator V3 (stream way version)"); + TestFilesV3(); + Console.WriteLine(); + WaitAndPress(); + + Console.WriteLine("* Files enumerator with a Yield method"); + TestFilesYield(); + Console.WriteLine(); + WaitAndPress(); + + Console.WriteLine("* Complex Yield method"); + TestYieldComplex(); + Console.WriteLine(); + WaitAndPress(); + #endregion + + #region LINQ samples + Console.WriteLine("* LINQ request"); + TestLinqRequest(); + Console.WriteLine(); + WaitAndPress(); + + Console.WriteLine("* Fluent LINQ request"); + TestLinqRequestAsFluent(); + Console.WriteLine(); + WaitAndPress(); + + Console.WriteLine("* LINQ simulation with methods"); + TestSimulLinq(); + Console.WriteLine(); + WaitAndPress(); + #endregion + } + } +} diff --git a/Enumerables-Technet/Source/Enumerables-en/Properties/AssemblyInfo.cs b/Enumerables-Technet/Source/Enumerables-en/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..0e5e10e --- /dev/null +++ b/Enumerables-Technet/Source/Enumerables-en/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// Les informations générales relatives à un assembly dépendent de +// l'ensemble d'attributs suivant. Changez les valeurs de ces attributs pour modifier les informations +// associées à un assembly. +[assembly: AssemblyTitle("Enumerables-en")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Enumerables-en")] +[assembly: AssemblyCopyright("Copyright © 2015")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// L'affectation de la valeur false à ComVisible rend les types invisibles dans cet assembly +// aux composants COM. Si vous devez accéder à un type dans cet assembly à partir de +// COM, affectez la valeur true à l'attribut ComVisible sur ce type. +[assembly: ComVisible(false)] + +// Le GUID suivant est pour l'ID de la typelib si ce projet est exposé à COM +[assembly: Guid("a948ab06-3663-455f-be3a-7d83801eb949")] + +// Les informations de version pour un assembly se composent des quatre valeurs suivantes : +// +// Version principale +// Version secondaire +// Numéro de build +// Révision +// +// Vous pouvez spécifier toutes les valeurs ou indiquer les numéros de build et de révision par défaut +// en utilisant '*', comme indiqué ci-dessous : +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Enumerables-Technet/Source/Enumerables-en/ReverseEnumerable.cs b/Enumerables-Technet/Source/Enumerables-en/ReverseEnumerable.cs new file mode 100644 index 0000000..97e2242 --- /dev/null +++ b/Enumerables-Technet/Source/Enumerables-en/ReverseEnumerable.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Enumerables +{ + /// + /// IEnumerable implementation to read a list in the reverse way + /// + public class ReverseEnumerable : IEnumerable + { + + /// + /// New enumerable class + /// + public ReverseEnumerable(IList source) + { + this.List = source; + } + + /// + /// IEnumerable<T>.GetEnumerator() implementation + /// + /// Create a new ReverseEnumerator + public IEnumerator GetEnumerator() + { + return new ReverseEnumerator(List); + } + + /// + /// IEnumerable.GetEnumerator() (non generic version) + /// + /// + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + /// + /// Source list + /// + public IList List { get; private set; } + + } +} diff --git a/Enumerables-Technet/Source/Enumerables-en/ReverseEnumerator.cs b/Enumerables-Technet/Source/Enumerables-en/ReverseEnumerator.cs new file mode 100644 index 0000000..d13e191 --- /dev/null +++ b/Enumerables-Technet/Source/Enumerables-en/ReverseEnumerator.cs @@ -0,0 +1,107 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Enumerables +{ + + /// + /// Enumerator iterates the list in the reverse way + /// + public class ReverseEnumerator : IEnumerator + { + IList _Source; + int _Position; + bool _Completed; + + /// + /// Create a new enumerator + /// + public ReverseEnumerator(IList source) + { + this._Source = source; + // Set -1 to indicates the iteration is not started + this._Position = -1; + // The iteration is not finished + this._Completed = false; + // Set the Current value by default + this.Current = default(T); + } + + /// + /// Release the resources + /// + public void Dispose() + { + // Nothing to dispose, but mark the iterator as finished + this._Completed = true; + Console.WriteLine("Enumerator disposed"); + } + + /// + /// This method is called when we want to reset the enumerator + /// + public void Reset() + { + // Set -1 to indicates the iteration is not started + this._Position = -1; + // The iteration is not finished + this._Completed = false; + // Set the Current value by default + this.Current = default(T); + } + + /// + /// We go to the next element + /// + /// False when the iteration is finished + public bool MoveNext() + { + // If the source is null then we have nothing to browse, the iteration is finished + if (this._Source == null) return false; + + // If the iteration is finished, we stop here + if (this._Completed) return false; + + // If the is -1 we get the count of the elements to iterates for starting the iteration + if (this._Position == -1) + { + Console.WriteLine("Iteration started"); + this._Position = _Source.Count; + } + + // We move on the list + this._Position--; + + // If we reach the -1 position the iteration is finished + if (this._Position < 0) + { + this._Completed = true; + Console.WriteLine("Iteration finished"); + return false; + } + + // We set Current and continue + Current = this._Source[this._Position]; + + return true; + } + + /// + /// Current element + /// + public T Current { get; private set; } + + /// + /// Current element for the non generic version + /// + object System.Collections.IEnumerator.Current + { + get { return Current; } + } + + } + +} diff --git a/Enumerables-Technet/Source/Enumerables-fr/App.config b/Enumerables-Technet/Source/Enumerables-fr/App.config new file mode 100644 index 0000000..9c05822 --- /dev/null +++ b/Enumerables-Technet/Source/Enumerables-fr/App.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/Enumerables-Technet/Source/Enumerables-fr/EnumFilesV1.cs b/Enumerables-Technet/Source/Enumerables-fr/EnumFilesV1.cs new file mode 100644 index 0000000..9061393 --- /dev/null +++ b/Enumerables-Technet/Source/Enumerables-fr/EnumFilesV1.cs @@ -0,0 +1,90 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Enumerables +{ + /// + /// Enumérable parcourant les lignes texte d'un ensemble de fichier + /// + public class EnumFilesV1 : IEnumerable + { + + private List _Lines; + + /// + /// Création d'un nouvel énumérable + /// + public EnumFilesV1(IEnumerable files) + { + // Initialisation des fichiers + this.Files = files.ToArray(); + + // On marque la liste des lignes à charger en la mettant à null + _Lines = null; + } + + void LoadFiles() + { + // Création de la liste des lignes + _Lines = new List(); + + if (this.Files != null) + { + // Pour chaque fichier + foreach (var file in Files) + { + try + { + // Ouverture d'un lecteur de fichier texte + using (var reader = new StreamReader(file)) + { + // Lecture de chaque ligne du fichier + String line; + while ((line = reader.ReadLine()) != null) + { + // On ajoute la ligne dans la liste + _Lines.Add(line); + } + } + } + catch { } // Si une erreur à la lecture du fichier on passe au prochain fichier + } + } + } + + /// + /// Retourne l'énumérateur des lignes + /// + public IEnumerator GetEnumerator() + { + // Si la liste des lignes est null alors il faut lire les fichiers + if (_Lines == null) + { + // Chargement des fichiers + LoadFiles(); + } + + // Retourne l'énumérateur de la liste + return _Lines.GetEnumerator(); + } + + /// + /// Implémentation de IEnumerator.GetEnumerator() (version non générique) + /// + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + /// + /// Liste des fichiers + /// + public String[] Files { get; private set; } + + } + +} diff --git a/Enumerables-Technet/Source/Enumerables-fr/EnumFilesV2.cs b/Enumerables-Technet/Source/Enumerables-fr/EnumFilesV2.cs new file mode 100644 index 0000000..5702183 --- /dev/null +++ b/Enumerables-Technet/Source/Enumerables-fr/EnumFilesV2.cs @@ -0,0 +1,84 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Enumerables +{ + /// + /// Enumérable parcourant les lignes texte d'un ensemble de fichier reconstruisant une liste à chaque appel + /// + public class EnumFilesV2 : IEnumerable + { + + /// + /// Création d'un nouvel énumérable + /// + public EnumFilesV2(IEnumerable files) + { + // Initialisation des fichiers + this.Files = files.ToArray(); + } + + IList LoadFiles() + { + // Création de la liste des lignes + var result = new List(); + + if (this.Files != null) + { + // Pour chaque fichier + foreach (var file in Files) + { + try + { + // Ouverture d'un lecteur de fichier texte + using (var reader = new StreamReader(file)) + { + // Lecture de chaque ligne du fichier + String line; + while ((line = reader.ReadLine()) != null) + { + // On ajoute la ligne dans la liste + result.Add(line); + } + } + } + catch { } // Si une erreur à lecture du fichier on passe au prochain fichier + } + } + + // On retourne la liste + return result; + } + + /// + /// Retourne l'énumérateur des lignes + /// + public IEnumerator GetEnumerator() + { + // On construit la liste + var list = LoadFiles(); + + // Retourne l'énumérateur de la liste + return list.GetEnumerator(); + } + + /// + /// Implémentation de IEnumerator.GetEnumerator() (version non générique) + /// + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + /// + /// Liste des fichiers + /// + public String[] Files { get; private set; } + + } + +} diff --git a/Enumerables-Technet/Source/Enumerables-fr/EnumFilesV3.cs b/Enumerables-Technet/Source/Enumerables-fr/EnumFilesV3.cs new file mode 100644 index 0000000..ea98919 --- /dev/null +++ b/Enumerables-Technet/Source/Enumerables-fr/EnumFilesV3.cs @@ -0,0 +1,209 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Enumerables +{ + /// + /// Enumérable parcourant les lignes texte d'un ensemble de fichier via un énumérateur + /// + public class EnumFilesV3 : IEnumerable + { + + /// + /// Création d'un nouvel énumérable + /// + public EnumFilesV3(IEnumerable files) + { + // Initialisation des fichiers + this.Files = files.ToArray(); + } + + /// + /// Retourne l'énumérateur des lignes + /// + public IEnumerator GetEnumerator() + { + // Retourne un nouvel énumérateur + return new FileEnumerator(Files); + } + + /// + /// Implémentation de IEnumerator.GetEnumerator() (version non générique) + /// + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + /// + /// Liste des fichiers + /// + public String[] Files { get; private set; } + + } + + /// + /// Enumérateur de fichier + /// + class FileEnumerator : IEnumerator + { + Func _CurrentState = null; + int _CurrentFilePos; + String _CurrentFileName; + TextReader _CurrentFile; + + /// + /// Création d'un nouvel énumérateur + /// + public FileEnumerator(String[] files) + { + // Initialisation des fichiers + this.Files = files; + // Initialisation de l'énumérateur + Current = null; + _CurrentFilePos = 0; + _CurrentFileName = null; + _CurrentFile = null; + // L'état de l'énumérateur est à l'ouverture du prochain fichier à traiter + _CurrentState = OpenNextFileState; + } + + /// + /// Libération des ressources éventuelles + /// + public void Dispose() + { + // Si on a un fichier encore d'ouvert on libère la mémoire + if (_CurrentFile != null) + { + _CurrentFile.Dispose(); + _CurrentFile = null; + } + // On défini l'état 'Completed' + _CurrentState = CompletedState; + } + + /// + /// Essayes d'ouvrir le prochain fichier de la liste + /// + bool GetNextFile() + { + String filename = null; + TextReader file = null; + while (file == null && Files != null && _CurrentFilePos < Files.Length) + { + try + { + filename = Files[_CurrentFilePos++]; + file = new StreamReader(filename); + } + catch { } + } + // Si on a un fichier d'ouvert + if (file != null) + { + _CurrentFileName = filename; + _CurrentFile = file; + return true; + } + // Sinon on a rien trouvé + return false; + } + + /// + /// Ouverture du prochain fichier + /// + bool OpenNextFileState() + { + // Si on a pas ou plus de fichier on arrête tout + if (!GetNextFile()) + { + Current = null; + // On passe à l'état 'Completed' + _CurrentState = CompletedState; + // On termine + return false; + } + + // On passe à l'état ReadNextLine + _CurrentState = ReadNextLineState; + + // On lit la première ligne + return _CurrentState(); + } + + /// + /// On est en cours de lecture + /// + bool ReadNextLineState() + { + try + { + // On lit la prochaine ligne du fichier + String line = _CurrentFile.ReadLine(); + // Si la ligne n'est pas null on la traite + if (line != null) + { + Current = line; + return true; + } + // La ligne est null alors on a atteint la fin du fichier, on libère sa ressource + } + catch + { + // Si une erreur survient à la lecture on ferme le fichier en cours pour éviter les boucles infinies + + } + // Libération des ressources pour passer au fichier suivant + _CurrentFile.Dispose(); + _CurrentFile = null; + _CurrentFileName = null; + // On passe à l'état 'OpenNextFile' + _CurrentState = OpenNextFileState; + // On traite le prochain état + return _CurrentState(); + } + + /// + /// L'itération est terminée on retourne toujours false + /// + bool CompletedState() + { + return false; + } + + /// + /// On ne s'occupe pas de cette méthode + /// + public void Reset() + { + throw new NotSupportedException(); + } + + /// + /// On se déplace + /// + public bool MoveNext() + { + // Exécution de l'état en cours + return _CurrentState(); + } + + /// + /// Liste des fichiers + /// + public String[] Files { get; private set; } + + /// + /// Valeur en cours + /// + public string Current { get; private set; } + object System.Collections.IEnumerator.Current { get { return Current; } } + + } + +} diff --git a/Enumerables-Technet/Source/Enumerables-fr/EnumerableWithEnumeratorDisposable.cs b/Enumerables-Technet/Source/Enumerables-fr/EnumerableWithEnumeratorDisposable.cs new file mode 100644 index 0000000..90c30fe --- /dev/null +++ b/Enumerables-Technet/Source/Enumerables-fr/EnumerableWithEnumeratorDisposable.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Enumerables +{ + /// + /// Classe énumérable utilisant des énumérateurs disposable + /// + class EnumerableWithEnumeratorDisposable : IEnumerable + { + List _Source; + + /// + /// Création d'un nouvel énumérable + /// + /// Source d'initialisation + /// Action invoquée lorsqu'un énumérateur est disposé + public EnumerableWithEnumeratorDisposable(IEnumerable source, Action onDispose = null) + { + _Source = new List(source); + OnDispose = onDispose; + } + + /// + /// Retourne un énumérateur disposable + /// + public IEnumerator GetEnumerator() + { + return new EnumeratorDisposable(_Source, OnDispose); + } + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + /// + /// Action invoquée lorsqu'un énumérateur est disposé + /// + public Action OnDispose { get; set; } + + } + +} diff --git a/Enumerables-Technet/Source/Enumerables-fr/Enumerables-fr.csproj b/Enumerables-Technet/Source/Enumerables-fr/Enumerables-fr.csproj new file mode 100644 index 0000000..5b78f6a --- /dev/null +++ b/Enumerables-Technet/Source/Enumerables-fr/Enumerables-fr.csproj @@ -0,0 +1,80 @@ + + + + + Debug + AnyCPU + {BD6CD85F-9196-4DFB-BEDC-560004EB982A} + Exe + Properties + Enumerables + Enumerables-fr + v4.5.1 + 512 + true + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Always + + + Always + + + Always + + + + + \ No newline at end of file diff --git a/Enumerables-Technet/Source/Enumerables-fr/EnumeratorDisposable.cs b/Enumerables-Technet/Source/Enumerables-fr/EnumeratorDisposable.cs new file mode 100644 index 0000000..7c1402d --- /dev/null +++ b/Enumerables-Technet/Source/Enumerables-fr/EnumeratorDisposable.cs @@ -0,0 +1,61 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Enumerables +{ + /// + /// Enumérateur disposable + /// + class EnumeratorDisposable : IEnumerator, IDisposable + { + List _Source; + int _Current = -1; + + /// + /// Création d'un nouvel énuméraeur + /// + /// Source à énumérer + /// Action invoqué lors du Dispose + public EnumeratorDisposable(IEnumerable source, Action onDispose) + { + this._Source = new List(source); + this.OnDispose = onDispose; + } + /// + /// Dispose + /// + public void Dispose() + { + if (OnDispose != null) + OnDispose(); + } + /// + /// Item en cours + /// + public T Current { get; private set; } + object System.Collections.IEnumerator.Current { get { return Current; } } + /// + /// Déplacement vers le prochain item + /// + public bool MoveNext() + { + if (++_Current >= _Source.Count) return false; + Current = _Source[_Current]; + return true; + } + + public void Reset() + { + throw new NotImplementedException(); + } + + /// + /// Méthode invoquée lorsque l'énnumérateur est disposé + /// + public Action OnDispose { get; set; } + } + +} diff --git a/Enumerables-Technet/Source/Enumerables-fr/FakeEnumerable.cs b/Enumerables-Technet/Source/Enumerables-fr/FakeEnumerable.cs new file mode 100644 index 0000000..b997c3e --- /dev/null +++ b/Enumerables-Technet/Source/Enumerables-fr/FakeEnumerable.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Enumerables +{ + /// + /// Faux énumérable n'implémentant pas IEnumerable mais supporté par foreach + /// + class FakeEnumerable + { + /// + /// foreach requierd uniquement une méthode publique GetEnumerator() qui retourne un objet qui contient une méthode publique + /// MoveNext() et une propriété Current. + /// + public FakeEnumerator GetEnumerator() + { + return new FakeEnumerator(); + } + } + +} diff --git a/Enumerables-Technet/Source/Enumerables-fr/FakeEnumerator.cs b/Enumerables-Technet/Source/Enumerables-fr/FakeEnumerator.cs new file mode 100644 index 0000000..161cef3 --- /dev/null +++ b/Enumerables-Technet/Source/Enumerables-fr/FakeEnumerator.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Enumerables +{ + /// + /// Faux énumérateur n'implémentant pas IEnumerator mais supporté par foreach + /// + /// + /// Doit implémenter une méthode publique MoveNext() et une propriété Current + /// + class FakeEnumerator + { + List _Source = new List(new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }); + int _Current = -1; + /// + /// Item en cours + /// + public int Current { get; private set; } + /// + /// Déplacement vers le prochain item + /// + public bool MoveNext() + { + if (++_Current >= _Source.Count) return false; + Current = _Source[_Current]; + return true; + } + } +} diff --git a/Enumerables-Technet/Source/Enumerables-fr/Files/Fichier1.txt b/Enumerables-Technet/Source/Enumerables-fr/Files/Fichier1.txt new file mode 100644 index 0000000..6241aba --- /dev/null +++ b/Enumerables-Technet/Source/Enumerables-fr/Files/Fichier1.txt @@ -0,0 +1,3 @@ +Première ligne du premier fichier. +Deuxième ligne du premier fichier. +Troisième ligne du premier fichier. diff --git a/Enumerables-Technet/Source/Enumerables-fr/Files/Fichier2.txt b/Enumerables-Technet/Source/Enumerables-fr/Files/Fichier2.txt new file mode 100644 index 0000000..6cf2956 --- /dev/null +++ b/Enumerables-Technet/Source/Enumerables-fr/Files/Fichier2.txt @@ -0,0 +1,3 @@ +Première ligne du second fichier. +Deuxième ligne du second fichier. +Troisième ligne du second fichier. diff --git a/Enumerables-Technet/Source/Enumerables-fr/Files/Fichier3.txt b/Enumerables-Technet/Source/Enumerables-fr/Files/Fichier3.txt new file mode 100644 index 0000000..f984105 --- /dev/null +++ b/Enumerables-Technet/Source/Enumerables-fr/Files/Fichier3.txt @@ -0,0 +1,3 @@ +Première ligne du troisième fichier. +Deuxième ligne du troisième fichier. +Troisième ligne du troisième fichier. diff --git a/Enumerables-Technet/Source/Enumerables-fr/Person.cs b/Enumerables-Technet/Source/Enumerables-fr/Person.cs new file mode 100644 index 0000000..a5446b1 --- /dev/null +++ b/Enumerables-Technet/Source/Enumerables-fr/Person.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Enumerables +{ + class Person + { + public String Name { get; set; } + public DateTime? BirthDay { get; set; } + public bool IsFamily { get; set; } + } +} diff --git a/Enumerables-Technet/Source/Enumerables-fr/Program.cs b/Enumerables-Technet/Source/Enumerables-fr/Program.cs new file mode 100644 index 0000000..67bff4c --- /dev/null +++ b/Enumerables-Technet/Source/Enumerables-fr/Program.cs @@ -0,0 +1,586 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Enumerables +{ + class Program + { + + /// + /// Retourne une liste d'éléments en tant qu'enumérable + /// + /// Indique si on retourne un énumérable avec énumérateur disposable + static IEnumerable GetItems(bool asDisposable = false) + { + var items = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; + return asDisposable ? new EnumerableWithEnumeratorDisposable(items, () => Console.WriteLine("Enumérateur Disposé")).AsEnumerable() : items; + } + + /// + /// Liste des fichiers sources + /// + static IEnumerable GetTestFileNames() + { + var dir = AppDomain.CurrentDomain.BaseDirectory; + return new String[]{ + Path.Combine(dir, "Files", "Fichier1.txt"), + Path.Combine(dir, "Files", "Fichier2.txt"), + Path.Combine(dir, "Files", "Fichier3.txt") + }; + } + + /// + /// Liste de personnes + /// + static IEnumerable GetPersons() + { + yield return new Person() { + Name = "Toto", + BirthDay = null, + IsFamily = true + }; + yield return new Person() { + Name = "Titi", + BirthDay = new DateTime(2010, 11, 27), + IsFamily = false + }; + yield return new Person() { + Name = "Tata", + BirthDay = null, + IsFamily = false + }; + yield return new Person() { + Name = "Tutu", + BirthDay = new DateTime(1994, 8, 11), + IsFamily = true + }; + } + + #region Exemple boucles + + /// + /// Exemple foreach + /// + static void ForEach() + { + // Récupération de l'énumérable + IEnumerable enumerable = GetItems(); + + // Parcours chaque élément dans 'enumerable' + foreach (int elm in enumerable) + { + // .. + Console.WriteLine(elm); + } + } + + /// + /// Exemple foreach avec des énumérateur disposable + /// + static void ForEachDisposable() + { + // Récupération de l'énumérable + IEnumerable enumerable = GetItems(true); + + // Parcours chaque élément dans 'enumerable' + foreach (int elm in enumerable) + { + // .. + Console.WriteLine(elm); + } + } + + /// + /// Exemple d'itération de base + /// + static void IterationBase() + { + // Récupération de l'énumérable + IEnumerable enumerable = GetItems(); + + // Récupère un nouvel énumérateur + IEnumerator enumerator = enumerable.GetEnumerator(); + // Tant que l'énumérateur se déplace + while (enumerator.MoveNext()) + { + Int32 elm = enumerator.Current; + // .. + Console.WriteLine(elm); + } + } + + /// + /// Exemple d'itération avec gestion du IDisposable tel que le fait un foreach + /// + static void IterationBaseWithDispose() + { + // Récupération de l'énumérable + IEnumerable enumerable = GetItems(true); + + // Récupère un nouvel énumérateur + IEnumerator enumerator = enumerable.GetEnumerator(); + try + { + // Tant que l'énumérateur se déplace + while (enumerator.MoveNext()) + { + Int32 elm = enumerator.Current; + // .. + Console.WriteLine(elm); + } + } + finally + { + // On détermine si l'énumérateur est disposable + IDisposable disp = enumerator as IDisposable; + // Si c'est le cas on dispose l'énumérateur + if (disp != null) + { + disp.Dispose(); + } + } + } + + /// + /// Exemple de foreach avec un objet n'implémentant pas IEnumerable/IEnumerator + /// + static void ForEachWithoutIEnumerable() + { + // Création du faux énumérable + var enumerable = new FakeEnumerable(); + + // Parcours chaque élément dans 'enumerable' + foreach (int elm in enumerable) + { + // .. + Console.WriteLine(elm); + } + } + + /// + /// Test ReverseEnumerator + /// + static void TestReverse() + { + // ReverseEnumerator attend un IList<> + IList list = new List(GetItems()); + + // Création de l'énumerable reverse + IEnumerable enumerable = new ReverseEnumerable(list); + + // Parcours chaque élément dans elm + foreach (int elm in enumerable) + { + // .. + Console.WriteLine(elm); + } + + // On parcours chaque élément mais en arrêtant la boucle quand on trouve l'élément 5 + foreach (int elm in enumerable) + { + if (elm == 5) break; + // .. + Console.WriteLine(elm); + } + + } + + #endregion + + #region Exemple énumérateurs complexes + + /// + /// Test de l'énumérateur de fichier première version + /// + static void TestFilesV1() + { + // Création de l'énumérable avec les fichiers de tests + var enumerable = new EnumFilesV1(GetTestFileNames()); + + // On parcours l'énumérable + foreach (var line in enumerable) + { + Console.WriteLine(line); + } + + } + + /// + /// Test de l'énumérateur améliorant un peut la première version + /// + static void TestFilesV2() + { + // Création de l'énumérable avec les fichiers de tests + var enumerable = new EnumFilesV2(GetTestFileNames()); + + // On parcours l'énumérable + foreach (var line in enumerable) + { + Console.WriteLine(line); + } + + } + + /// + /// Test de l'énumérateur de fichiers plus subtile + /// + static void TestFilesV3() + { + // Création de l'énumérable avec les fichiers de tests + var enumerable = new EnumFilesV3(GetTestFileNames()); + + // On parcours l'énumérable + foreach (var line in enumerable) + { + Console.WriteLine(line); + } + + // On parcours l'énumérable et provoque un arrêt prématuré + int i = 0; + foreach (var line in enumerable) + { + if (i++ >= 4) break; + Console.WriteLine(line); + } + } + + #endregion + + #region Exemples méthode yield + + /// + /// Ouvre un nouveau fichier ou retourne null si une erreur à lieu + /// + static StreamReader OpenFile(String file) + { + try + { + return new StreamReader(file); + } + catch + { + return null; + } + } + /// + /// Enumération des fichiers avec une méthode Yield + /// + static IEnumerable EnumFilesYield(IEnumerable files) + { + if (files != null) + { + // Pour chaque fichier + foreach (var file in files) + { + // Ouverture d'un lecteur de fichier texte + using (var reader = OpenFile(file)) + { + // reader peut être null si une erreur à eu lieu + if (reader != null) + { + // Lecture de chaque ligne du fichier + String line; + do + { + // Lecture d'une ligne, si une erreur à lieu on arrête la boucle + try + { + line = reader.ReadLine(); + } + catch + { + break; + } + // On envoi la ligne d'énumérable + if (line != null) + yield return line; + } while (line != null);// Boucle tant qu'on a une ligne + } + } + } + } + } + /// + /// Test la méthode yield d'énumération + /// + static void TestFilesYield() + { + // Récupération d'un 'énumérable avec les fichiers de tests + var enumerable = EnumFilesYield(GetTestFileNames()); + + // On parcours l'énumérable + foreach (var line in enumerable) + { + Console.WriteLine(line); + } + + // On parcours l'énumérable et provoque un arrêt prématuré + int i = 0; + foreach (var line in enumerable) + { + if (i++ >= 4) break; + Console.WriteLine(line); + } + + } + /// + /// Même méthode que mais sans gestion des exception + /// + static IEnumerable EnumFilesYieldUnprotected(IEnumerable files) + { + if (files != null) + { + // Pour chaque fichier + foreach (var file in files) + { + // Ouverture d'un lecteur de fichier texte + using (var reader = new StreamReader(file)) + { + // Lecture de chaque ligne du fichier + String line; + while ((line = reader.ReadLine()) != null) + { + // On ajoute la ligne dans la liste + yield return line; + } + } + } + } + } + + /// + /// Méthode yield complexe + /// + static IEnumerable YieldComplex() + { + Random rnd = new Random(); + + // Emission directe + yield return "Start"; + + // Emission dans un switch + for (int i = 0; i < 10; i++) + { + switch (rnd.Next(4)) + { + case 1: + yield return "Funky 1"; + break; + case 2: + continue; + case 3: + yield return "<<<<"; + // Emission d'un autre énum + foreach (var line in EnumFilesYield(GetTestFileNames())) + { + // On place une condition + if (line.Contains("x")) + yield return line; + } + yield return ">>>>"; + break; + case 0: + default: + yield return "Funky 0"; + break; + } + } + + // Emission directe + yield return "End"; + } + + /// + /// Test la méthode yield complexe + /// + static void TestYieldComplex() + { + foreach (var item in YieldComplex()) + Console.WriteLine(item); + } + + #endregion + + #region Exemples LINQ + + /// + /// Simple requête LINQ + /// + static void TestLinqRequest() + { + var query = from p in GetPersons() + where p.IsFamily + orderby p.Name + select p; + + foreach (var p in query) + Console.WriteLine(p.Name); + + foreach (var p in query.Take(2)) + Console.WriteLine(p.Name); + } + + /// + /// Simple requête LINQ en mode fluent + /// + static void TestLinqRequestAsFluent() + { + var query = GetPersons() + .Where(p => p.IsFamily) + .OrderBy(p => p.Name) + ; + + foreach (var p in query) + Console.WriteLine(p.Name); + + foreach (var p in query.Take(2)) + Console.WriteLine(p.Name); + } + + //static void TestStats() + //{ + // var q = from stats in GetStats() + // where stat.Date >= lastRefresh + // orderby stat.Date + // select stats; + + // foreach (var stats in q) + // { + // // Affichage de la stat + // } + + // lblLastUpdate.Text = String.Format("{0} : {1} nouvelles statistiques", DateTime.Now, q.Count()); + // lastRefresh = DateTime.Now; + + //} + + /// + /// Simulation de l'opérateur Where + /// + static IEnumerable SimulWhere() + { + foreach (var p in GetPersons()) + { + if (p.IsFamily) + yield return p; + } + } + + /// + /// Simulation de l'opérateur OrderBy + /// + static IEnumerable SimulOrderBy() + { + List result = new List(); + result.AddRange(SimulWhere()); + result.Sort((x, y) => String.Compare(x.Name, y.Name)); + foreach (var p in result) + yield return p; + } + + /// + /// Simulation LINQ + /// + static void TestSimulLinq() + { + var query = SimulOrderBy(); + + foreach (var p in query) + Console.WriteLine(p.Name); + + foreach (var p in query.Take(2)) + Console.WriteLine(p.Name); + } + + #endregion + + static void WaitAndPress() + { + Console.WriteLine("Appuyer sur une touche pour continuer..."); + Console.ReadKey(); + Console.WriteLine(); + } + static void Main(string[] args) + { + #region Exemple boucles + Console.WriteLine("* Boucle ForEach"); + ForEach(); + Console.WriteLine(); + WaitAndPress(); + + Console.WriteLine("* Boucle ForEach avec un Disposable"); + ForEachDisposable(); + Console.WriteLine(); + WaitAndPress(); + + Console.WriteLine("* Itération de base (simulation ForEach)"); + IterationBase(); + Console.WriteLine(); + WaitAndPress(); + + Console.WriteLine("* Itération de base avec un disposable (simulation ForEach)"); + IterationBaseWithDispose(); + Console.WriteLine(); + WaitAndPress(); + + Console.WriteLine("* Boucle ForEach avec un objet non IEnumerable"); + ForEachWithoutIEnumerable(); + Console.WriteLine(); + WaitAndPress(); + + Console.WriteLine("* Test ReverseEnumerator"); + TestReverse(); + Console.WriteLine(); + WaitAndPress(); + #endregion + + #region Exemples énumérateurs complexes et méthode yield + Console.WriteLine("* Enumérateur de fichier V1 (approche basique)"); + TestFilesV1(); + Console.WriteLine(); + WaitAndPress(); + + Console.WriteLine("* Enumérateur de fichier V2 (version précédente améliorée)"); + TestFilesV2(); + Console.WriteLine(); + WaitAndPress(); + + Console.WriteLine("* Enumérateur de fichier V3 (version avec une approche flux)"); + TestFilesV3(); + Console.WriteLine(); + WaitAndPress(); + + Console.WriteLine("* Enumérateur de fichier avec une méthode Yield"); + TestFilesYield(); + Console.WriteLine(); + WaitAndPress(); + + Console.WriteLine("* Méthode Yield complexe"); + TestYieldComplex(); + Console.WriteLine(); + WaitAndPress(); + #endregion + + #region Exemple LINQ + Console.WriteLine("* Requête LINQ"); + TestLinqRequest(); + Console.WriteLine(); + WaitAndPress(); + + Console.WriteLine("* Requête LINQ fluent"); + TestLinqRequestAsFluent(); + Console.WriteLine(); + WaitAndPress(); + + Console.WriteLine("* Simulation de LINQ avec des méthodes"); + TestSimulLinq(); + Console.WriteLine(); + WaitAndPress(); + #endregion + } + } +} diff --git a/Enumerables-Technet/Source/Enumerables-fr/Properties/AssemblyInfo.cs b/Enumerables-Technet/Source/Enumerables-fr/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..51beddc --- /dev/null +++ b/Enumerables-Technet/Source/Enumerables-fr/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// Les informations générales relatives à un assembly dépendent de +// l'ensemble d'attributs suivant. Changez les valeurs de ces attributs pour modifier les informations +// associées à un assembly. +[assembly: AssemblyTitle("Enumerables-fr")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Enumerables-fr")] +[assembly: AssemblyCopyright("Copyright © 2015")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// L'affectation de la valeur false à ComVisible rend les types invisibles dans cet assembly +// aux composants COM. Si vous devez accéder à un type dans cet assembly à partir de +// COM, affectez la valeur true à l'attribut ComVisible sur ce type. +[assembly: ComVisible(false)] + +// Le GUID suivant est pour l'ID de la typelib si ce projet est exposé à COM +[assembly: Guid("b18cdda5-64a0-4310-9f54-314fa6fe6591")] + +// Les informations de version pour un assembly se composent des quatre valeurs suivantes : +// +// Version principale +// Version secondaire +// Numéro de build +// Révision +// +// Vous pouvez spécifier toutes les valeurs ou indiquer les numéros de build et de révision par défaut +// en utilisant '*', comme indiqué ci-dessous : +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Enumerables-Technet/Source/Enumerables-fr/ReverseEnumerable.cs b/Enumerables-Technet/Source/Enumerables-fr/ReverseEnumerable.cs new file mode 100644 index 0000000..3d7a90b --- /dev/null +++ b/Enumerables-Technet/Source/Enumerables-fr/ReverseEnumerable.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Enumerables +{ + /// + /// Implémentation de IEnumerable permettant de parcourir une liste dans le sens inverse + /// + public class ReverseEnumerable : IEnumerable + { + + /// + /// Nouvelle classe énumérable + /// + public ReverseEnumerable(IList source) + { + this.List = source; + } + + /// + /// Implémentation de IEnumerable<T>.GetEnumerator() + /// + /// Créé un nouveau ReverseEnumerator + public IEnumerator GetEnumerator() + { + return new ReverseEnumerator(List); + } + + /// + /// IEnumerable.GetEnumerator() (version non générique) + /// + /// + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + /// + /// Liste source + /// + public IList List { get; private set; } + + } +} diff --git a/Enumerables-Technet/Source/Enumerables-fr/ReverseEnumerator.cs b/Enumerables-Technet/Source/Enumerables-fr/ReverseEnumerator.cs new file mode 100644 index 0000000..1d970d6 --- /dev/null +++ b/Enumerables-Technet/Source/Enumerables-fr/ReverseEnumerator.cs @@ -0,0 +1,107 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Enumerables +{ + + /// + /// Enumérateur parcourant une liste dans le sens inverse + /// + public class ReverseEnumerator : IEnumerator + { + IList _Source; + int _Position; + bool _Completed; + + /// + /// Création d'un nouvel énumérateur + /// + public ReverseEnumerator(IList source) + { + this._Source = source; + // On met -1 pour indiquer qu'on a pas commencé l'itération + this._Position = -1; + // L'itération n'est pas terminée + this._Completed = false; + // On défini Current avec la valeur par défaut + this.Current = default(T); + } + + /// + /// Libération des ressources + /// + public void Dispose() + { + // On a rien à libérer , mais on marque notre itérateur comme terminé + this._Completed = true; + Console.WriteLine("Enumerateur disposé"); + } + + /// + /// Cette méthode est appelée lorsque l'on veut réinitialiser l'énumérateur + /// + public void Reset() + { + // On met -1 pour indiquer qu'on a pas commencer l'itération + this._Position = -1; + // L'itération n'est pas terminée + this._Completed = false; + // On défini Current avec la valeur par défaut + this.Current = default(T); + } + + /// + /// On se déplace vers le prochain élément + /// + /// False lorsque l'itération est terminée + public bool MoveNext() + { + // Si la source est Null alors on a rien à parcourir, donc l'itération s'arrête + if (this._Source == null) return false; + + // Si l'itération est terminée alors on ne va pas plus loin + if (this._Completed) return false; + + // Si la position est à -1 on récupère le nombre d'éléments à parcourir pour démarrer l'itération + if (this._Position == -1) + { + Console.WriteLine("Itération commencée"); + this._Position = _Source.Count; + } + + // On se déplace dans la liste + this._Position--; + + // Si on a atteind -1 alors on a terminé l'itération + if (this._Position < 0) + { + this._Completed = true; + Console.WriteLine("Itération terminée"); + return false; + } + + // On défini Current et on continue + Current = this._Source[this._Position]; + + return true; + } + + /// + /// Elément en cours de l'itération + /// + public T Current { get; private set; } + + /// + /// Elément en cours pour la version non générique + /// + object System.Collections.IEnumerator.Current + { + get { return Current; } + } + + } + +} diff --git a/Enumerables-Technet/Source/Enumerables.sln b/Enumerables-Technet/Source/Enumerables.sln new file mode 100644 index 0000000..52521b0 --- /dev/null +++ b/Enumerables-Technet/Source/Enumerables.sln @@ -0,0 +1,34 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2013 +VisualStudioVersion = 12.0.40629.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Enumerables-fr", "Enumerables-fr\Enumerables-fr.csproj", "{BD6CD85F-9196-4DFB-BEDC-560004EB982A}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Enumerables-en", "Enumerables-en\Enumerables-en.csproj", "{6373827A-FF33-4A4F-A7F7-934903CD17EA}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Articles", "Articles", "{83ADC8F3-65D4-4FF8-8B52-9614700F9E86}" + ProjectSection(SolutionItems) = preProject + ..\Article\article-en.md = ..\Article\article-en.md + ..\Article\article-fr.md = ..\Article\article-fr.md + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {BD6CD85F-9196-4DFB-BEDC-560004EB982A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BD6CD85F-9196-4DFB-BEDC-560004EB982A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BD6CD85F-9196-4DFB-BEDC-560004EB982A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BD6CD85F-9196-4DFB-BEDC-560004EB982A}.Release|Any CPU.Build.0 = Release|Any CPU + {6373827A-FF33-4A4F-A7F7-934903CD17EA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6373827A-FF33-4A4F-A7F7-934903CD17EA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6373827A-FF33-4A4F-A7F7-934903CD17EA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6373827A-FF33-4A4F-A7F7-934903CD17EA}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal