-
Li
chevron_right
Port complet de TapTempo en C# 9
xcomcmdr · pubsub.eckmul.net / linuxfr · Sunday, 10 January, 2021 - 23:02 · 8 minutes
- label
<h2 class="sommaire">Sommaire</h2>
<ul class="toc">
<li><a href="#toc-les-sources">Les sources</a></li>
<li><a href="#toc-pourquoi">Pourquoi ?</a></li>
<li><a href="#toc-ce-qui-a-%C3%A9t%C3%A9-port%C3%A9">Ce qui a été porté</a></li>
<li><a href="#toc-ce-qui-est-propre-%C3%A0-ce-port">Ce qui est propre à ce port</a></li>
<li><a href="#toc-ce-que-jai-loup%C3%A9">Ce que j'ai loupé</a></li>
<li><a href="#toc-note-%C3%A0-moi-m%C3%AAme">Note à moi-même</a></li>
</ul>
<p>Bonjour 'nal,</p>
<p>Y'a plein de poussière ici dit moi. Faut dire que ça fait longtemps que je ne t'ai plus écrit.</p>
<p>Ce WE, j'ai voulu faire honneur à Pierre Tramo avec un langage qui (à ses débuts) était une copie de Java avec quelques améliorations pour pas que le prof voit qu'on a copié sur le voisin.</p>
<p>Je veux bien sûr parler de C#.</p>
<h2 id="toc-les-sources">Les sources</h2>
<p><a href="https://github.com/maximilien-noal/SharpTempo">C'est par là</a></p>
<h2 id="toc-pourquoi">Pourquoi ?</h2>
<p>Parce que j'en avais envie. Pour le fun. Parce que ça manquait (ou pas), surtout face au port Java.</p>
<h2 id="toc-ce-qui-a-été-porté">Ce qui a été porté</h2>
<p>Tout. Les traductions, les tests unitaires, le mode jeu, l'aspect orienté-objet, …</p>
<h2 id="toc-ce-qui-est-propre-à-ce-port">Ce qui est propre à ce port</h2>
<p>J'ai voulu utiliser au plus les possibilités modernes de C# 9 et précédents.</p>
<p>Bon y'a pas de <a href="https://docs.microsoft.com/en-us/archive/msdn-magazine/2019/may/csharp-8-0-pattern-matching-in-csharp-8-0">pattern matching</a> ni d'async/await ni de <a href="https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/operators/stackalloc">stackalloc</a>, ni plein d'autres choses, mais c'est pour très une bonne raison : on en a pas besoin.</p>
<p>Le point d'entré utilise des <em>top level statements</em> (C# 9) (et une lambda) :</p>
<pre><code>using CmdTempo;
using CommandLine;
using CommandLine.Text;
using LibTempo;
// Set sentence builder to localizable
SentenceBuilder.Factory = () => new LocalizableSentenceBuilder();
Parser.Default.ParseArguments<Options>(args).WithParsed((options) => Runner.RunOptions(options));
</code></pre>
<p>Comme pour <a href="https://blog.stephencleary.com/2012/02/async-console-programs.html">async dans un point d'entrée de programme console</a>, ce n'est que du sucre syntaxique.<br>
Le compilateur s'occupe de rajouter tout la sauce (namespace, classe statique, static void Main (string[] args)).</p>
<p>Mais ça fait du bien à mes petits doigts potelés de ne pas avoir à l'écrire, et à mes yeux de ne pas avoir à les lire. Et ça, c'est bon !</p>
<p>On utilise parfois des <a href="https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-8.0/ranges">ranges</a> sur des chaînes de caractère (C# 8) :</p>
<pre><code>return
String.Format(Resource.SentenceMutuallyExclusiveSetErrors, names[0..^2], incompat[0..^2]);
</code></pre>
<p>C'est beaucoup plus rapide à écrire qu'avec <a href="https://docs.microsoft.com/en-us/dotnet/api/system.string.substring?view=net-5.0">SubString</a>, et ça me rappelle <a href="https://ruby-doc.org/core-2.5.1/Range.html">mes années Ruby</a>.</p>
<p>Les options sont une classe essayant de forcer l'immutabilité (<a href="https://devblogs.microsoft.com/dotnet/c-9-0-on-the-record/">un record</a>) (C# 9) :</p>
<pre><code>namespace LibTempo
{
using CommandLine;
public record Options
{
public const uint DefaultSampleSize = 5;
public const uint DefaultResetTime = 5;
public const uint DefaultPrecision = 0;
public const uint MaxPrecision = 5;
[Option('g', "game", Required = false, Default = false, HelpText = nameof(IsGamingMode), ResourceType = typeof(Resource))]
public bool IsGamingMode { get; }
[Option('s', "sample-size", Required = true, Default = DefaultSampleSize, HelpText = nameof(SampleSize), ResourceType = typeof(Resource))]
public uint SampleSize { get; }
[Option('r', "reset-time", Required = true, Default = DefaultResetTime, HelpText = nameof(ResetTime), ResourceType = typeof(Resource))]
public uint ResetTime { get; }
[Option('p', "precision", Required = true, Default = DefaultPrecision, HelpText = nameof(Precision), ResourceType = typeof(Resource))]
public uint Precision { get; }
public Options(bool isGamingMode, uint sampleSize, uint resetTime, uint precision)
{
IsGamingMode = isGamingMode;
SampleSize = sampleSize == 0 ? DefaultSampleSize : sampleSize;
ResetTime = resetTime == 0 ? DefaultResetTime : resetTime;
Precision = precision > MaxPrecision ? MaxPrecision : precision == 0 ? DefaultPrecision : precision;
}
}
}
</code></pre>
<p>On utilise des extensions pour faire croire que nous aussi on a Back, Front, et IsEmpty dans la classe générique <a href="https://docs.microsoft.com/en-us/dotnet/api/system.collections.generic.queue-1?view=net-5.0">Queue</a> (C# 2 pour les <a href="https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/generics/">Generics</a> et 3 pour <a href="https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/linq/introduction-to-linq-queries">LINQ</a>, ça nous rajeunit pas) :</p>
<pre><code>using System.Collections.Generic;
using System.Linq;
namespace LibTempo
{
internal static class QueueExtensions
{
public static bool IsEmpty<T>(this Queue<T> queue) => queue.Count == 0;
public static T Back<T>(this Queue<T> queue) => queue.Last();
public static T Front<T>(this Queue<T> queue) => queue.First();
}
}
</code></pre>
<p>On utilise partout des <a href="https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/statements-expressions-operators/expression-bodied-members"><em>expression body</em></a> pour des méthodes car les {} et return c'est <em>has-been</em> (C# 7) :</p>
<pre><code>private double ComputeNewSecretBPM() => _betterRng.Next(50, 200);
</code></pre>
<p>On écrit le BPM avec la précision demandée <a href="https://docs.microsoft.com/en-us/dotnet/standard/base-types/standard-numeric-format-strings">en utilisant beaucoup moins le clavier</a> et avec de l'<a href="https://docs.microsoft.com/en-us/dotnet/csharp/tutorials/string-interpolation">interpolation</a> (C# 7) :</p>
<pre><code>protected string BPMToStringWithPrecision(double bpm) => bpm.ToString($"G{_precision}", CultureInfo.CurrentCulture);
</code></pre>
<p>Et le pattern Fluent pour des tests plus rapidement écrits (mais ça c'est un package Nuget) :</p>
<pre><code> [Fact]
public void InvalidArsShouldReturnDefaultOptions()
{
var result = Parser.Default.ParseArguments<Options>(args: new string[] { "0", "0" });
result.Tag.Should().Be(ParserResultType.NotParsed);
}
</code></pre>
<p>On a activé les <a href="https://docs.microsoft.com/en-us/dotnet/csharp/nullable-references">Nullable Reference Types</a> partout, parce que c'est la gueere contre les NullReferenceExceptions depuis C# 8 (comme beaucoup de choses, C# a piqué ça à F# où NULL n'existe pas) :</p>
<pre><code> <Nullable>enable</Nullable>
<TreatWarningsAsErrors>True</TreatWarningsAsErrors>
</code></pre>
<p>Ainsi, un accès à null (qui provoque un crash) est détecté à la compilation et traité comme une erreur.<br>
Mais je n'ai pas eu l’occasion d'utiliser les annotations, donc pas de code à montrer. Le compilateur n'a rien dit.</p>
<h2 id="toc-ce-que-jai-loupé">Ce que j'ai loupé</h2>
<p>J'aurais pu convertir le projet en <a href="https://marketplace.visualstudio.com/items?itemName=SharpDevelopTeam.CodeConverter">VB.NET en un tour de main grâce au compilateur Roslyn</a>, afficher la console dans le navigateur avec <a href="https://github.com/ardacetinkaya/Blazor.Console">Blazor.Console</a>… Peut-être plus tard ?</p>
<p>J'aurais pu aussi utiliser <a href="https://github.com/VitaliiTsilnyk/NGettext">NGetText</a> pour éviter les fichiers RESX et leur intrusion avec leurs clés en <a href="https://wiki.c2.com/?PascalCase">Pascal Case</a> dans le code, ce qui le rend moins lisible. Et ainsi garder le fichier .PO d'origine.</p>
<pre><code>Console.WriteLine(Resource.HitEnterForEachTempoOrQToQuit);
</code></pre>
<p>Enfin, j'ai loupé mon week-end, et j'aurais pu me coucher plus tôt.</p>
<p>Sacré Pierre Tramo !</p>
<h2 id="toc-note-à-moi-même">Note à moi-même</h2>
<p>Ce n'est pas parce que c'est plus facile de compiler et déboguer <a href="https://taptempo.tuxfamily.org/">TapTempo</a> quand on est sous Windows avec <a href="https://doc.ubuntu-fr.org/wsl">WSL</a> qu'il fallait forcément t'y intéresser !</p>
<div><a href="https://linuxfr.org/users/xcomcmdr/journaux/port-complet-de-taptempo-en-c-9.epub">Télécharger ce contenu au format EPUB</a></div> <p>
<strong>Commentaires :</strong>
<a href="//linuxfr.org/nodes/122890/comments.atom">voir le flux Atom</a>
<a href="https://linuxfr.org/users/xcomcmdr/journaux/port-complet-de-taptempo-en-c-9#comments">ouvrir dans le navigateur</a>
</p>