Archives pour la catégorie C#

C#

HoloLens 2 et Azure Kinect : Microsoft ne lâche rien

​Du 25 au 28 février, donc pile cette semaine, se déroule le Mobile World Congress 2019. Un rendez-vous assez important dans le milieu des appareils mobiles, que ce soit évidemment les smartphones, tablettes, PC portable, ou casque de VR. Un salon parfait que Microsoft a choisi pour présenter ses deux nouveaux produits : HoloLens 2 et Azure Kinect Developer Kit

Lire la suite

C# 7.3 : les nouveautés

Petite évolution de C# 7.3 qui mérite notre attention, à nous fans de C#, du framework .NET et de ses apports. Microsoft a en effet apporté une attention particulière à la performance du code en mode sécurisé, à une amélioration substantielle de fonctionnalités existantes mais aussi deux nouvelles options de compilateur qui vont ravir ceux qui aiment l’opensource.

Que devons-nous retenir ?

Amélioration des performances du code sécurisé

L’indexation des champs fixed ne nécessite plus un second fixed

Vous le savez probablement, même si on en parle peu, même si on l’utilise peu, C# nous permet la création de code en mode non sécurisé. Cette écriture est très pratique pour ceux qui travaillent sur des fonctionnalités pointues où les performances sont essentielles. L’apport de C# 7.3 est de faciliter le contexte entre code sécurisé et code non-sécurisé afin que les développeurs aient justement moins accès au code non sécurisé. En effet, moins les développeurs passent de temps en code non sécurisé, plus leur code est stabilisé et sûr.

Observer par exemple dans le code ci-dessous la différence entre un code pré-C# 7.3 et un code C# 7.3. Dans cet exemple, vous devez accéder à un élément d’un tableau d’entier à l’intérieur d’une structure « unsafe »

unsafe struct S
{
   public fixed int myFixedField[10];
}

//Avant c# 7.3
class Avant
{
   static S s = new S();

   unsafe public void MaMethod()
   {
      fixed (int* ptr = s.myFixedField)
      {
         int p = ptr[5];
      }
   }
}

//Apres c# 7.3
class Apres
{
   static S s = new S();

   unsafe public void MaMethod()
   {
      int p = s.myFixedField[5];
   }
}

Avec C# 7.3, il n’y a plus besoin d’épingler avec fixed un pointeur complémentaire vers l’élément ; il y a a toutefois toujours besoin d’un contexte unsafe.

L’utilisation de fixed est étendue

L’instruction fixed, qui se limitait à un nombre limite de types est étendu à tout type contenant une méthode GetPinnableReference() qui retoure un ref T ou ref readonly T. L’instruction fixed est par exemple utilisable avec System.Span<T>.

Initialiseurs sur stackalloc

A l’image des initialiseurs sur les tableaux « classiques », il est possible d’utiliser la syntaxe suivante avec stackalloc (allocation d’un bloc mémoire sur la pile)

int* pArr = stackalloc int[3] {1, 2, 3};
int* pArr2 = stackalloc int[] {1, 2, 3};
Span arr = stackalloc [] {1, 2, 3};

Réaffectation des variables locales ref

Très pratique, les variables locales ref peuvent être réaffectées ; ce qui était auparavant impossible.

ref MaStructure  a = ref Structure1;
a = ref Structure2; //réassignation

Quelques nouveautés

Comparaisons et différences sur les tuples

Tous d’abord les tuples prennent désormais en charge les comparaisons et les différences. Je vous invite vivement à lire ce document Microsoft, mais voici en résumé le comportement : les tuples peuvent se comparer grâce à une comparaison dans l’ordre des membres de gauche à ceux de droites, les comparaisons s’arrêtant dès qu’une paire n’est pas égale.

var left = (a: 5, b: 10);
var right = (a: 5, b: 10);
Console.WriteLine(left == right); // affiche 'true'

De nombreuses choses se passent de façon transparente, notamment des conversions implicites comme dans l’exemple ci-dessous où la comparaison de int à long se fait implicitement

(long a, int b) longFirst = (5, 10);
(int a, long b) longSecond = (5, 10);
Console.WriteLine(longFirst == longSecond); // Affiche vrai

Des conversions « lifted », grosso modo des conversions qui gère le cas des comparaisons de type Nullable s’opèrent également

var left = (a: 5, b: 10);
(int? a, int? b) nullableMembers = (5, 10);
Console.WriteLine(left == nullableMembers); // Vrai

 Les contraintes « where » sur les génériques sont améliorées

Qu’il est pratique ce « where » sur les génériques ; il permet de spécifier l’héritage mais également s’il est possible de créer une instance comme dans l’exemple suivant où le type T doit implémenter IComparable mais également avoir un constructeur accessible sans paramètre.

public class MyGenericClass where T : IComparable, new()
{
// La ligne suivante est impossible sans la contrainte new():
T item = new T();
}

Vous connaissez et probablement utilisez déjà les contraintes de type struct ou class comme dans l’exemple suivant.

class MyClass<T, U>
where T : class
where U : struct
{ }

Ils sont en revanche nouveaux grâce à C# 7.3, ce sont les contraintes de type Enum, Delegate ou MulticastDelegate .

public class UsingEnum where T : System.Enum { }

public class UsingDelegate where T : System.Delegate { }

public class Multicaster where T : System.MulticastDelegate { }

Avec C# 7.3 est égaleemnt ajouté les types non managés, c’est à dire les type qu’on ne sont pas un type référence et ne contiennent aucun champ de type référencé à tous les niveaux d’imbrication

class UnManagedWrapper
where T : unmanaged
{ }

Cibles d’attribut

La cible d’un attribut est l’entité à laquelle il s’applique. Les cibles peuvent être assembly, module, field, event, method, param, propery, return ou type. Il est désormais possible d’écrire :

[field: InformationSurLeFieldAttribute]
public int Couleur { get; set; }
{ }

Nouvelles options du compilateur

Signature publique/opensource

Il est désormais possible de signer un assembly avec une clé publique grâce à

-publicsign. 

L’assembly est ainsi identifié comme « signée » (grâce à un bit dans l’assembly) mais provient d’une clé publique. Ceci permet la génération d’assembly à partir de clé publique opensource.

pathmap

L’option

pathmap

 contrôle le chemin des sources écrits dans les fichiers PDB ou CallerFilePathAttribute. Il est ainsi possible d’indiquer au compilateur de remplacer les chemins sources de l’environnement de build par ceux mappés.

Cet article s’est focalisé sur les améliorations les plus parlantes, la liste complète de toutes les fonctionnalités est disponible  dans la documentation Microsoft.

 

C# 7.1 – Les nouveautés

Les nouveautés de C# 7.1 sont subtiles mais intéressante. Tout d’abord la sélection de la version du langage se fait dans les paramètres avancés du build. En effet, par défaut la version C# 7.1 n’est pas sélectionnée par défaut.

Il est également possible de modifier directement le fichier csproj en ajoutant un propertygroup approprié.

Async Main

Evolution, il est désormais possible d’utiliser le mot clé await dans la méthode Main. Il était en effet autrefois nécessaire d’encapsuler le retour d’une tâche asynchrone pour attendre le retour. Plusieurs techniques existaient entre la preview de l’Async CTP avec « GeneralThreadAffineContext », l’ « AsyncContext » du packge NuGet Nito.AsyncEX, c’est bien celle proposé par Microsoft la plus usité :

class Program
{
    static void Main(string[] args)
    {
        MainAsync(args).GetAwaiter().GetResult();
    }

    static async Task MainAsync(string[] args)
    {
        Bootstrapper bs = new Bootstrapper();
        var list = await bs.GetList();
    }
}

Avec C# 7.1, l’appel est nettement plus simple

class Program
{
    static async Task Main(string[] args)
    {
        Bootstrapper bs = new Bootstrapper();
        var list = await bs.GetList();
    }
}

Literal «default » et inférence de type

Le mot clé « default » est déjà particulièrement utile pour affecter une valeur par défaut à un géneric de type T dont on ne sait d’avance si c’est un type référence ou valeur. Ce mot clé peut par ailleurs être utilisé avec n’importe quel type managé comme default(string), default(int) ou default(int ?)

Avec C# 7.1, « default » peut être utilisé comme littéral si le compilateur peut faire un inférence de type. Autrement dit, au lieu d’écrire « default(T) », on peut littéralement écrire « defaut » si le compilateur a suffisamment d’information. Le code devient ainsi plus concis et clair ; cela marchant pour de nombreux cas comme la déclaration de valeurs par défaut de paramètre optionnel, le retour de méthodes, l’initialisation ou assignation de variables, etc .

public class Point
{
    public double X { get; }
    public double Y { get; }

    public Point(double x, double y)
    {
        X = x;
        Y = y;
    }
}

public class LabeledPoint
{
    public double X { get; private set; }
    public double Y { get; private set; }
    public string Label { get; set; }

    // Providing the value for a default argument:
    public LabeledPoint(double x, double y, string label = default)
    {
        X = x;
        Y = y;
        this.Label = label;
    }

    public static LabeledPoint MovePoint(LabeledPoint source,
        double xDistance, double yDistance)
    {
        // return a default value:
        if (source == null)
            return default;

        return new LabeledPoint(source.X + xDistance, source.Y + yDistance,
        source.Label);
    }

    public static LabeledPoint FindClosestLocation(IEnumerable<LabeledPoint> sequence,
        Point location)
    {
        // initialize variable:
        LabeledPoint rVal = default;
        double distance = double.MaxValue;

        foreach (var pt in sequence)
        {
            var thisDistance = Math.Sqrt((pt.X - location.X) * (pt.X - location.X) +
                (pt.Y - location.Y) * (pt.Y - location.Y));
            if (thisDistance < distance)
            {
                distance = thisDistance;
                rVal = pt;
            }
        }

        return rVal;
    }

    public static LabeledPoint ClosestToOrigin(IEnumerable<LabeledPoint> sequence)
        // Pass default value of an argument.
        => FindClosestLocation(sequence, default);
}

Inférence du nom des éléments dans un type.

C# 7.0. a introduit les tuples. Lors de leur initialisation d’un tuple, les noms souhaités pour ses éléments sont généralement ceux des variables utilisés comme dans l’exemple suivant

string nom = "Fabrice JEAN-FRANCOIS";
int age = 26;
var personne = (nom: nom, age: age);

 

Avec C# 7.1, les noms d’éléments de tuple peuvent désormais être déduits à partir de variables utilisés :

string nom = "Fabrice JEAN-FRANCOIS";
int age = 26;
var personne = (nom, age); // le nom des elements du tuple seront « nom » et « age »

Génération d’assembly de référence

Le compilateur peut générer des assemblies de référénce uniquement via /refout et refonly.

S’interfacer avec une Sphèro en remote avec NancyModule

 

using SpheroNET;

using System;

using System.Collections.Generic;

using System.Drawing;

using System.Linq;

using System.Text;

namespace SpheroConsole

{

public class SpheroModule : Nancy.NancyModule

{

static SpheroConnector spheroConnector = new SpheroConnector();

static Sphero sphero = null;

public SpheroModule()

{

Get[« / »] = _ =>

{

string s = « Novencia TS – WebSphero Proxy<br><br> »;

s += « Command List :<br/> »;

s += « <b>find<b> – Get port id Bluetooth devices<br/> »;

s += « <b>connect/{port}<b> – Connect to the given port id (see find)<br/> »;

s += « <b>sleep<b> – Sleep the Sphero<br/> »;

s += « <b>close<b> – Close the Sphero communication<br/> »;

s += « <b>setrgb/{red}/{green}/{blue}<b> -Change Sphero color<br/> »;

return s;

};

Get[« /find »] = _ =>

{

spheroConnector.Scan();

var deviceNames = spheroConnector.DeviceNames;

string result = «  »;

for (int i = 0; i < deviceNames.Count; i++)

{

result += string.Format(« {0}: {1}<br/> », i, deviceNames[i]);

}

return result;

};

Get[« /connect/{port} »] = PortFinder;

Get[« /close »] = _ => { spheroConnector.Close(); return « OK »; };

Get[« /sleep »] = _ => { sphero.Sleep(); return « OK »; };

Get[« /setrgb/{red}/{green}/{blue} »] = SetRGBColor;

Get[« /setrgb/{colorName} »] = SetRGBColor;

Get[« /goroll/{h}/{s}/{g} »] = Goroll;

Get[« /goroll/{h}/{s}/{g}/{d} »] = GorollWithDelay;

Post[« / »] = _ => « Hello world! »;

}

public string Goroll(dynamic o)

{

int h, s, g;

if (!int.TryParse(o.h, out h)) throw new Exception(« Invalid parameter »);

if (!int.TryParse(o.s, out s)) throw new Exception(« Invalid parameter »);

if (!int.TryParse(o.g, out g)) throw new Exception(« Invalid parameter »);

var programLines = new List<string>();

programLines.Add(« 10 basflg 1\r »);

programLines.Add(string.Format(« 20 goroll {0},{1},{2}\r »,h,s,g));

var area = StorageArea.Temporary;

sphero.EraseOrbBasicStorage(area);

sphero.SendOrbBasicProgram(area, programLines);

sphero.ExecuteOrbBasicProgram(area, 10);

return « OK »;

}

public string GorollWithDelay(dynamic o)

{

int h, s, g, d;

if (!int.TryParse(o.h, out h)) throw new Exception(« Invalid parameter »);

if (!int.TryParse(o.s, out s)) throw new Exception(« Invalid parameter »);

if (!int.TryParse(o.g, out g)) throw new Exception(« Invalid parameter »);

if (!int.TryParse(o.d, out d)) throw new Exception(« Invalid parameter »);

var programLines = new List<string>();

programLines.Add(« 10 basflg 1\r »);

programLines.Add(string.Format(« 20 goroll {0},{1},{2}\r », h, s, g));

programLines.Add(string.Format(« 30 deelay {0}\r », d));

var area = StorageArea.Temporary;

sphero.EraseOrbBasicStorage(area);

sphero.SendOrbBasicProgram(area, programLines);

sphero.ExecuteOrbBasicProgram(area, 10);

return « OK »;

}

public string SetRGBColor(dynamic o)

{

byte r, g, b;

try

{

r = byte.Parse(o.red);

g = byte.Parse(o.green);

b = byte.Parse(o.blue);

}

catch

{

string name = o.colorName;

Color c = Color.FromName(name);

r = c.R;

g = c.G;

b = c.B;

}

sphero.SetRGBLEDOutput(r, g, b);

return « OK »;

}

public string PortFinder(dynamic o)

{

int index = -1;

if (int.TryParse(o.port, out index))

{

if (o.port < 0) return « invalid parameter »;

try

{

sphero = spheroConnector.Connect(index);

return « OK »;

}

catch (Exception ex)

{

return ex.Message;

}

}

else

{

return string.Format(« ‘{0}’ is not a valid device index. », o.port);

}

}

}

}

 

WinRT

WinRT ou le retour en force du COM+

J’ai voulu ajouter « Lol » à la fin du titre de ce lien, mais il paraît que ça ne se fait pas.

On retrouve plein d’articles sur le NET nous parlant de WinRT, des incidences lorsqu’on doit développer en C#.
Je vous propose donc ce petit post, non pas pour vous parler de WinRT mais plutôt pour vous parler de COM(+) et vous expliquer ce choix, qui semble hasardeux, de Microsoft.

Tout d’abord, un petit rappel.

Observez le schéma de l’article, il a fait le tour du monde. Avec Windows 8, il y a deux façon de considérer Windows. La première en mode WinRT (partie de gauche), l’autre en mode desktop (partie de droite).

En mode WinRT, on vise un nouveau type d’application dites « Metro style Apps », dénomination valable jusqu’en août 2012 date où l’on appris que le terme était déposé et sujet à copyright. On parle désormais d’application ModernUI (ou Windows 8 style)
En mode Desktop, on développe classiquement, C++, C#, VB.NET, Assembleur

Beaucoup de développeurs .NET ont espéré, comme Java en son époque, que Windows allait devenir « CLR ». En d’autres termes, l’OS serait une grande machine virtuelle, on gagnerait en simplicité tout en se débarrassant de ces bonnes vieilles API Win32.

Mais que faire alors des anciennes applications justement Win32 ? Il aurait suffit de les virtualiser ! On le fait déjà quelque part pour faire fonctionner les applications 32bits (et même 16 bits!) sur du Windows 64 bits (merci WoW64^^). Développer une application tournant dans l’OS/CLR, qui émulerait les API Win32 et ferait fonctionner toute application non .NET dans cette Sandbox ! Simple ,non ?

Oui mais voilà, ce n’est absolument pas ce que Microsoft a fait ! Mais allons donc ? Pourquoi ? C’était si simple !

Disons que ce n’est pas parce qu’une chose a l’air simple en apparence qu’elle l’est dans les fait. Tout d’abord, au risque de passer pour un dinosaure, il faut savoir que depuis fort longtemps tout Windows est COM… ou DCOM… Enfin COM+. De quoi je parle ? Du fait que peut après l’apparition de la base de registre (Windows 95), Microsoft a planché sur des vrais soucis de communication inter logiciel : faire en sorte qu’un copier/coller fonctionne entre un Word et un Excel et mieux, que les logiciels échangent des comportements en temps réel (exporter un tableau Excel dans Word par Exemple). Microsoft a commencé par une technologie type OLE (Object Linking and Embedding) qu’ils ont rapidement muté en composant ActiveX et composant COM : les composants ActiveX étant « visuel » (comme une applet Java), les composants COM étant eux des composants sans interface.

Il faut imaginer dès l’époque que tout était bien pensé. On parlait de composants comme au sens moderne. Car à la différence de l’objet qui tourne dans un processus, le composant a une sorte d’existence propre, des entrées et des sorties (je vous passe les histoires de surrogate chez Microsoft^^). Vous avez bien lu ! J’ai fait un raccourci, mais un objet est une représentation binaire qui n’a de sens que dans son process ! Le composant, lui, existe en dehors !
Pour ces raisons, lorsque vous faites du WCF, vos objets exposés peuvent et même doivent prendre la dénomination de composants. Idem pour COM+, ce bon vieux CORBA (certes, on parle d’IOR pour des références d’objets interopérable à distance, mais la vision externe est un « composant »), CCM (pour ceux qui connaissent) et surtout les EJB !

OLE/COM date des années 1995… Et fut transformé en DCOM vers 1998 (Le D signifiant Distributed), technologie permettant d’appeler les composants COM à partir d’un autre ordinateur puis renommé COM+ dès les années 2000. COM+ est le mix de DCOM et d’une technologie transactionnelle (MTS – Microsoft Transaction Server) développée par Microsoft dans l’optique de concurrencer CORBA et ses service OTS (Object Transaction Service). Il faut imaginer MTS comme le service JTS de JEE, où la partie transactionnelle des EJB stateless (avec un peu de stateful mais avec des limitations beaucoup plus forte), où bien encore du WCF avec des TransactionFlow et autres TransactionScopeRequired configurés.

Quand .NET est apparu, il fut vanté par rapport à COM+ pour sa simplicité. Il ressemblait à Java, finit les DLL Hells (ou l’enfer des DLL car COM+ exigeaient des inscriptions en base de registre, le versioning était très compliqué).

Sauf que COM+, tandis que .NET évoluait, a énormément évolué de son côté… notamment en se débarrassant de l’obligation d’aller en base de registre… mais également en améliorant le versionning, la possibilité de faire du side-by-side, etc.

Le choix de faire du WinRT en « COM-like » est beaucoup plus logique qu’il n’y parait. Contrairement aux apparences, C++ ne peut pas être concurrencé par des technologies à base de VM sur un ordinateur. Tout simplement parce que, même si pour bon nombre de projets de gestion c’est suffisant, la performance ultime ne peut être atteinte qu’en se calant le plus possible à la machine : RAM, disques, processeurs, CPU et registres.
Pour ces raisons, Microsoft a fait évoluer son OS dans le bon sens, le rendant multiplateforme (bienvenue aux processeurs ARM) tout en faisant évoluer C++ !

Mais il ne faut pas se tromper. Quand on cible WinRT en C++, on développe en C++/CX (Component Extensions) comme l’on développait en C++/CLI (Common Language Infrastructure) lorsqu’on ciblait la CLR. Ca c’est la façon qui arrange tout le monde. C++ dans le même sac que C#, VB.NET, Javascript, etc… car c’est juste un autre langage, on ne gagne rien.

Demeure pour le puriste, que l’on peut faire du WRL (Windows Runtime C++ Library) ! WRL c’est l’équivalent d’ATL (Active Template Library) en COM+ ! Débarrassé de pratiquement tout, on attaque les choses sérieuses et on a latitude pour réaliser des appels à l’OS… d’un autre monde ;-)

Toutefois, même si je suis un aficionados d’ATL, et donc de WRL, je ne le recommande absolument pas en situation projet, à moins d’y être fortement contraint (pour des raisons de performances notamment, ou bien parce que vous développer un composant hyper technique). Car cela demande énormément de compétence et s’avère très difficile à la maintenance !