Tag Archives: Visual Studio

Propriétés et éléments MSBuild partagés avec Directory.Build.props

Pour être honnête, je n’ai jamais vraiment aimé MSBuild jusqu’à récemment. Les fichiers de projet générés par Visual Studio étaient immondes, l’essentiel de leur contenu était redondant, il fallait décharger les projets pour les éditer, c’était mal documenté… Mais avec l’avènement de .NET Core et du nouveau format de projet, plus léger, MSBuild est devenu un bien meilleur outil.

MSBuild 15 a introduit une nouvelle fonctionnalité assez sympa : les imports implicites (je ne sais pas si c’est le nom officiel, mais c’est celui que j’utiliserai). En gros, vous pouvez créer un fichier nommmé Directory.Build.props n’importe où dans votre solution, et il sera automatiquement importé par tous les projets sous le répertoire qui contient ce fichier. Cela permet de partager très facilement des propriétés et éléments communs entre les projets. Cette fonctionnalité est décrite en détail sur cette page.

Par exemple, si vous voulez partager certaines métadonnées entre plusieurs projets, créer simplement un fichier Directory.Build.props dans le dossier parent de vos projets :

<Project>

  <PropertyGroup>
    <Version>1.2.3</Version>
    <Authors>John Doe</Authors>
  </PropertyGroup>

</Project>

On peut aussi faire des choses plus intéressantes, comme activer et configurer StyleCop pour tous les projets :

<Project>

  <PropertyGroup>
    <!-- Common ruleset shared by all projects -->
    <CodeAnalysisRuleset>$(MSBuildThisFileDirectory)MyRules.ruleset</CodeAnalysisRuleset>
  </PropertyGroup>

  <ItemGroup>
    <!-- Add reference to StyleCop analyzers to all projects  -->
    <PackageReference Include="StyleCop.Analyzers" Version="1.0.2" />
    
    <!-- Common StyleCop configuration -->
    <AdditionalFiles Include="$(MSBuildThisFileDirectory)stylecop.json" />
  </ItemGroup>

</Project>

Notez que la variable $(MSBuildThisFileDirectory) fait référence au répertoire contenant le fichier MSBuild courant. Une autre variable utile est $(MSBuildProjectDirectory), qui fait référence au répertoire du projet en cours de génération.

MSBuild cherche le fichier Directory.Build.props en partant du répertoire du projet et en remontant les dossiers jusqu’à ce qu’il trouve un fichier correspondant, puis s’arrête de chercher. Dans certains cas, il peut être utile de définir des propriétés communes à tous les projets, et d’en ajouter d’autres qui ne s’appliquent qu’à un sous-répertoire. Pour faire cela, il faut que le fichier Directory.Build.props le plus "profond" importe explicitement celui du répertoire parent :

  • (rootDir)/Directory.build.props:
<Project>

  <!-- Properties common to all projects -->
  <!-- ... -->
  
</Project>
  • (rootDir)/tests/Directory.build.props:
<Project>

  <!-- Import parent Directory.build.props -->
  <Import Project="../Directory.Build.props" />

  <!-- Properties common to all test projects -->
  <!-- ... -->
  
</Project>

La documentation mentionne une autre approche, utilisant la fonction GetPathOfFileAbove, mais cela ne semblait pas fonctionner quand j’ai essayé… De toute façon, je pense qu’il est plus simple d’utiliser un chemin relatif, on risque moins de se tromper.

Utiliser les imports implicites apporte quelques avantages :

  • des fichiers de projet plus petits, puisque les propriétés et éléments identiques peuvent être factorisés dans des fichiers communs
  • un seul point de référence : si tous les projets référencent le même package NuGet, la version à référencer est définie à un seul endroit; il n’est plus possible d’avoir des incohérences.

Cette approche a cependant un inconvénient : Visual Studio n’a pas la notion de l’origine d’une variable ou d’un élément, donc si vous changez une propriété ou une référence de package dans l’IDE (via les pages de propriétés du projet ou le gestionnaire de packages NuGet), elle sera modifiée dans le fichier de projet lui-même, et non dans le fichier Directory.Build.props. De mon point de vue, ce n’est pas un gros problème, parce que j’ai pris l’habitude d’éditer les projets manuellement plutôt que d’utiliser l’IDE, mais ça peut être gênant pour certaines personnes.

Si vous voulez un exemple réel de l’utilisation de cette technique, jetez un oeil au repository de FakeItEasy, où nous utilisons plusieurs fichiers Directory.Build.props pour garder les projets propres et concis.

Notez que vous pouvez également créer un fichier Directory.Build.targets, suivant les mêmes principes, pour définir des cibles communes.

Tester et déboguer une bibliothèque depuis LINQPad

Cela faisait bien longtemps que je voulais bloguer à propos de LINQPad. Au cas où vous ne connaissez pas, LINQPad est un outil qui permet d’écrire et de tester du code très rapidement sans avoir besoin de créer un projet complet dans Visual Studio. Il supporte C#, VB.NET, F# et SQL. Il était initialement conçu comme un outil éducatif pour expérimenter avec LINQ (son auteur, Joe Albahari, l’avait développé pour accompagner son livre C# in a Nutshell), mais il est aussi extrêmement utile comme outil générique pour tester du code .NET.

J’utilise fréquemment LINQPad pour tester rapidement une bibliothèque sur laquelle je travaille. C’est très facile, il suffit de référencer l’assembly à tester et de l’utiliser normalement. Mais quand la bibliothèque ne se comporte pas comme prévu, il est souvent utile de pouvoir la déboguer pas à pas… Il s’avère que c’est assez simple à faire à partir de LINQPad !

La version premium de LINQPad a un débogueur intégré, qui n’est pas aussi puissant que celui de Visual Studio, mais quand même utile pour déboguer les scripts LINQPad. Cependant, il ne permet pas de rentrer dans le code de la bibliothèque… Heureusement, il y a une astuce qui permet d’utiliser le débogueur de Visual Studio pour déboguer le script qui s’exécuter dans LINQPad.

Tout d’abord, ouvrez dans Visual Studio la bibliothèque à déboguer, si ce n’est pas déjà fait. Compilez le projet, et ajoutez une référence à l’assembly dans votre script LINQPad:

Add reference to the library

Ecrivez du code qui utilise votre bibliothèque:

Use your library from LINQPad

Et ajoutez cette ligne au début du script LINQPad:

Debugger.Launch();

Quand vous exécutez le script, un dialogue va s’affichez pour vous demander de choisir un débogueur:

Choose a debugger

Sélectionnez l’instance de Visual Studio dans laquelle votre solution est ouverte et cliquez OK. Cela va attacher le débogueur de Visual Studio au processus qui exécute le script LINQPad, et suspendre l’exécution au niveau de l’appel à Debugger.Launch():

Debugging in Visual Studio

Vous pouvez maintenant déboguer votre script LINQPad et votre bibliothèque. Vous pouvez mettre des points d’arrêt, rentrer dans les méthodes, ajouter des espions, etc, exactement comme quand vous déboguez une application normale !

Tuples en C# 7

Un tuple est une liste finie et ordonnée de valeurs, éventuellement de types différents, et est utilisé pour regrouper des valeurs liées entre elles sans avoir à créer une type spécifique pour les contenir.

.NET 4.0 a introduit un ensemble de classes Tuple , qui s’utilisent de la façon suivante

private static Tuple<int, double> Tally(IEnumerable<double> values)
{
	int count = 0;
	double sum = 0.0;
	foreach (var value in values)
	{
	    count++;
	    sum += value;
	}
	return Tuple.Create(count, sum);
}

...

var values = ...
var t = Tally(values);
Console.WriteLine($"There are {t.Item1} values and their sum is {t.Item2}");

Les classes Tuple ont deux principaux inconvénients:

  • Ce sont des classes, c’est-à-dire des types référence. Cela implique qu’elles doivent être allouées sur le tas, et collectées par le garbage collector quand elles ne sont plus utilisées. Pour les applications où les performances sont critiques, cela peut être problématique. De plus, le fait qu’elles puissent être nulles n’est souvent pas souhaitable.
  • Les éléments du tuple n’ont pas de noms, ou plutôt, ils ont toujours les mêmes noms (Item1, Item2, etc), qui ne sont pas du tout significatifs. Le type Tuple<T1, T2> ne communique absolument aucune information sur ce que le tuple représente réellement, ce qui en fait un mauvais candidat pour les APIs publiques.

En C# 7, une nouvelle fonctionnalité sera introduite pour améliorer le support des tuples : il sera possible de déclarer des types tuple “inline”, un peu comme des types anonymes, à part qu’ils ne sont pas limités à la méthode courante. En utilisant cette feature, le code précédent devient beaucoup plus clair:

static (int count, double sum) Tally(IEnumerable<double> values)
{
	int count = 0;
	double sum = 0.0;
	foreach (var value in values)
	{
	    count++;
	    sum += value;
	}
	return (count, sum);
}

...

var values = ...
var t = Tally(values);
Console.WriteLine($"There are {t.count} values and their sum is {t.sum}");

Notez comment le type de retour de Tally est déclaré, et comment le résultat est utilisé. C’est beaucoup mieux ! Les éléments du tuple ont maintenant des noms significatifs, et la syntaxe est plus agréable. Cette fonctionnalité repose sur un nouveau type ValueTuple<T1, T2>, qui est une structure et ne nécessite donc pas d’allocation sur le tas.

Vous pouvez essayer cette feature dès maintenant dans Visual Studio 15 Preview 3. Cependant, le type ValueTuple<T1, T2> ne fait pas (encore) partie du .NET Framework; pour faire fonctionner cet exemple, il faudra installer le package NuGet System.ValueTuple.

Enfin, une dernière remarque concernant les noms des membres du tuple : comme beaucoup d’autres fonctionnalités du langage, c’est juste du sucre syntaxique. Dans le code compilé, les membres du tuple sont référencés en tant que Item1 et Item2, et non count et sum. La méthode Tally renvoie en fait un ValueTuple<int, double>, et non un type spécialement généré.

Notez que le compilateur qui est livré avec VS 15 Preview 3 ne génère aucune métadonnée concernant les noms des membres du tuple. Cette partie de la feature n’est pas encore implémentée, mais devrait être incluse dans la version finale. Cela signifie qu’en attendant, on ne peut pas utiliser de tuples entre différents assemblies (enfin, on peut, mais en perdant les noms des membres, il faudra donc utiliser Item1 et Item2 pour y faire référence).

Essai des fonctionnalités de C# 7 dans Visual Studio “15” Preview

Il y a environ deux semaines, Microsoft a publié la première version préliminaire de la prochaine mouture de Visual Studio. Vous pourrez découvrir toutes les nouveautés qu’elle contient dans les notes de version. Il y a quelques nouveautés vraiment sympa (j’aime particulièrement le nouvel “installeur léger”), mais le plus intéressant pour moi est que le compilateur C# livré avec inclut quelques unes des fonctionnalités prévues pour C# 7. Regardons ça de plus près !

Activer les nouvelles fonctionnalités

Les nouvelles fonctionnalités ne sont pas activées par défaut. On peut les activer individuellement avec l’option /feature: en ligne de commande, mais le plus simple est de toutes les activer en ajoutant __DEMO__ et __DEMO_EXPERIMENTAL__ dans les symboles de compilation conditionnelle (Propriétés du projet, onglet Build).

Fonctions locales

La plupart des langages fonctionnels permettent de déclarer des fonctions dans le corps d’autres fonctions. Il est maintenant possible de faire la même chose en C# 7 ! La syntaxe pour déclarer une méthode dans une autre est sans grande surprise :

long Factorial(int n)
{
    long Fact(int i, long acc)
    {
        return i == 0 ? acc : Fact(i - 1, acc * i);
    }
    return Fact(n, 1);
}

Ici, la méthode Fact est locale à la méthode Factorial (au cas où vous vous posez la question, c’est une implémentation avec récursion terminale de la factorielle — ce qui n’a pas beaucoup de sens, vu que C# ne supporte pas la récursion terminale, mais bon, c’est juste un exemple…).

Bien sûr, il était déjà possible de simuler une fonction locale à l’aide d’une expression lambda, mais cela avait plusieurs inconvénients :

  • c’est moins lisible, car il faut déclarer explicitement le type du delegate ;
  • c’est plus lent, à cause du coût de création d’une instance de delegate, et de l’appel via le delegate ;
  • l’écriture d’expressions lambda récursives est peu intuitive.

Voici les principaux avantages des fonctions locales :

  • quand une méthode est utilisée seulement comme auxiliaire d’une autre méthode, la rendre locale permet de rendre cette relation plus explicite;
  • comme les lambdas, une fonction locale peut capturer les variables et paramètres de la méthode qui la contient;
  • les fonctions locales supportent la récursion comme n’importe quelle autre méthode

Pour en savoir plus sur les fonctions locales, allez voir sur le dépôt Github de Roslyn.

Valeurs de retour et variables locales par référence

Il est possible, depuis la première version de C#, de passer des paramètres par référence, ce qui est conceptuellement similaire au fait de passer un pointeur dans un langage comme C. Jusqu’ici, cette possibilité était limitée aux paramètres, mais C# 7 l’étend aux valeurs de retour et aux variables locales. Voici un exemple :

static void TestRefReturn()
{
    var foo = new Foo();
    Console.WriteLine(foo); // 0, 0
     
    foo.GetByRef("x") = 42;
 
    ref int y = ref foo.GetByRef("y");
    y = 99;
 
    Console.WriteLine(foo); // 42, 99
}
 
class Foo
{
    private int x;
    private int y;
 
    public ref int GetByRef(string name)
    {
        if (name == "x")
            return ref x;
        if (name == "y")
            return ref y;
        throw new ArgumentException(nameof(name));
    }
 
    public override string ToString() => $"{x},{y}";
}

Examinons ce code d’un peu plus près.

  • A la ligne 6, on dirait que j’affecte une valeur à une méthode. Qu’est-ce que ça peut bien vouloir dire ? Eh bien, la méthode GetByRef renvoie un champ de la classe Foo par référence (notez le type de retour ref int). Si je passe "x" en argument, elle renvoie le champ x par référence (et non une copie de la valeur du champ). Donc si j’affecte une valeur à cela, la valeur du champ x est également modifiée.
  • A la ligne 8, au lieu de simplement affecter une valeur directement au champ renvoyé par GetByRef, je passe par une variable locale y. Cette variable partage maintenant le même emplacement mémoire que le champ foo.y. Si j’affecte une valeur à y, cela modifie donc aussi foo.y.

Notez qu’il est également possible de renvoyer par référence un emplacement dans un tableau :

private MyBigStruct[] array = new MyBigStruct[10];
private int current;
 
public ref MyBigStruct GetCurrentItem()
{
    return ref array[current];
}

Il est probable que la plupart des développeurs C# n’auront jamais besoin de cette fonctionnalité; elle est assez bas-niveau, et ce n’est pas le genre de chose dont on a habituellement besoin quand on écrit des applications métier. Cependant, elle est très utile pour du code dont les performances sont critiques : copier une structure de grande taille est assez couteux, donc avoir la possibilité de la renvoyer plutôt par référence peut représenter un gain de performance non négligeable.

Pour en savoir plus sur cette fonctionnalité, rendez-vous sur Github.

Pattern matching

Le pattern matching (parfois traduit en “filtrage par motif”, mais je trouve cette traduction un peu bancale) est une fonctionnalité très courante dans les langages fonctionnels. C# 7 introduit certains aspects du pattern matching, sous forme d’extensions de l’opérateur is. Par exemple, quand on teste le type d’une variable, on peut maintenant introduire une nouvelle variable après le type, de façon à ce que cette variable reçoive la valeur de l’opérande de gauche, mais avec le type indiqué par l’opérande de droite (ce sera plus clair dans un instant avec un exemple).

Par exemple, si vous devez tester qu’une valeur est de type DateTime, puis faire quelque chose avec ce DateTime, il faut actuellement tester le type, puis convertir la valeur vers ce type :

object o = GetValue();
if (o is DateTime)
{
    var d = (DateTime)o;
    // Do something with d
}

En C# 7, on peut simplifier un peu :

object o = GetValue();
if (o is DateTime d)
{
    // Do something with d
}

d est maintenant déclarée directement dans l’expression o is DateTime.

Cette fonctionnalité peut aussi être utilisée dans un block switch :

object v = GetValue();
switch (v)
{
    case string s:
        Console.WriteLine($"{v} is a string of length {s.Length}");
        break;
    case int i:
        Console.WriteLine($"{v} is an {(i % 2 == 0 ? "even" : "odd")} int");
        break;
    default:
        Console.WriteLine($"{v} is something else");
        break;
}

Dans ce code, chaque case introduit une variable du type approprié, qu’on peut ensuite utiliser dans le corps du case.

Pour l’instant, je n’ai couvert que le pattern matching avec un simple test du type, mais il existe aussi des formes plus sophistiquées. Par exemple :

switch (DateTime.Today)
{
    case DateTime(*, 10, 31):
        Console.WriteLine("Happy Halloween!");
        break;
    case DateTime(var year, 7, 4) when year > 1776:
        Console.WriteLine("Happy Independance Day!");
        break;
    case DateTime { DayOfWeek is DayOfWeek.Saturday }:
    case DateTime { DayOfWeek is DayOfWeek.Sunday }:
        Console.WriteLine("Have a nice week-end!");
        break;
    default:
        break;
}

Plutôt cool, non ?

Le pattern matching existe aussi sous une autre forme (encore expérimentale), avec un nouveau mot-clé match :

object o = GetValue();
string description = o match
    (
        case string s : $"{o} is a string of length {s.Length}"
        case int i : $"{o} is an {(i % 2 == 0 ? "even" : "odd")} int"
        case * : $"{o} is something else"
    );

Cela ressemble beaucoup à un switch, sauf que c’est une expression et non une instruction.

Il y a encore beaucoup à dire sur le pattern matching, mais ça devient un peu long pour un article de blog, je vous invite donc à consulter la spec sur Github pour plus de détails.

Littéraux binaires et séparateurs de chiffres

Ces fonctionnalités n’étaient pas explicitement mentionnées dans les notes de version, mais j’ai remarqué qu’elles étaient inclues aussi. Elles étaient initialement prévues pour C# 6, mais avaient finalement été retirées avant la release ; elles sont de retour en C# 7.

Il est maintenant possible d’écrire des littéraux numériques sous forme binaire, en plus du décimal et de l’hexadécimal :

int x = 0b11001010;

Pratique pour définir des masques de bits !

De plus, pour rendre les grands nombres plus lisibles, on peut désormais grouper les chiffres en introduisant des séparateurs. Cela fonctionne avec les littéraux décimaux, hexadécimaux, et binaires :

int oneBillion = 1_000_000_000;
int foo = 0x7FFF_1234;
int bar = 0b1001_0110_1010_0101;

Conclusion

Visual Studio “15” Preview vous permet donc de commencer à expérimenter avec les nouvelles fonctionnalités de C# 7 ; n’hésitez pas à faire part de vos retours sur Github ! Et gardez à l’esprit que ce n’est qu’une version préliminaire, beaucoup de choses peuvent encore changer avant la version finale.

Intégration avec Visual Studio Online + Git dans Team Explorer

J’ai commencé récemment à utiliser Visual Studio Online pour des projets personnels, et je dois dire que c’est une très bonne plateforme, même si ce serait bien de pouvoir héberger des projets publics et non pas seulement privés. J’apprécie particulièrement l’intégration dans le Team Explorer de Visual Studio pour gérer les tâches et les builds.

Cependant j’ai remarqué un petit bug quand on utilise Git pour le contrôle de version : le remote pour VS Online doit s’appeler origin, sinon Team Explorer ne détecte pas qu’il s’agit d’un projet VS Online, et n’affiche pas les pages “Builds” et “Work Items”.

Quand le remote VSO s'appelle "origin"Quand le remote VSO s'appelle "vso"

C’est clairement un bug (quoique mineur), car le nom origin est juste une convention, et un remote Git peut s’appeler n’importe comment ; je l’ai signalé sur Connect. Si vous rencontrez ce problème, vous pouvez le contourner en renommant le remote en origin :

git remote rename vso origin

Exécuter un outil personnalisé automatiquement quand un fichier est modifié

Aussi loin que je me souvienne, il y a toujours eu dans Visual Studio quelque chose appelé “outils personnalisés” (custom tools), également connus sous le nom de single-file generators. Quand vous appliquez un tel outil à un fichier de votre projet, il génère quelque chose (généralement du code, mais pas forcément) en fonction du contenu du fichier. Par exemple, l’outil personnalisé par défaut pour les fichiers de ressource s’appelle ResXFileCodeGenerator, et génère une classe qui permet d’accéder facilement aux ressources définies dans le fichier resx.

image

Quand vous enregistrez un fichier qui a un outil personnalisé associé, Visual Studio réexécute automatiquement l’outil personnalisé pour regénérer sa sortie. Vous pouvez aussi le faire manuellement, en utilisant la commande “Exécuter l’outil personnalisé” depuis le menu contextuel du fichier dans l’explorateur de solution.

Habituellement, les outils personnalisés ne se basent que sur un fichier d’entrée pour générer leur sortie, mais parfois les choses sont un peu plus complexes. Par exemple, prenons les templates T4 : ils ont un outil personnalisé associé (TextTemplatingFileGenerator), donc cet outil est exécuté quand le template est sauvegardé, mais bien souvent, le template lui-même utilise d’autres fichiers d’entrée pour générer sa sortie. Donc l’outil personnalisé doit être exécuté non seulement quand le template est modifié, mais également quand les fichiers dont il dépend sont modifiés. Puisqu’il n’y a pas de moyen d’indiquer à Visual Studio l’existence de cette dépendance, il faut exécuter l’outil personnalisé manuellement, ce qui est assez agaçant…

Comme j’étais dans cette situation, et que j’en avais assez d’aller exécuter manuellement l’outil personnalisé sur mes templates T4, j’ai finalement créé une extension Visual Studio pour le faire automatiquement : AutoRunCustomTool. Le nom manque un peu d’imagination, mais au moins il est descriptif…

Cet outil est conçu pour être très simple et discret : il fait son travail en silence, sans vous gêner, et vous oubliez très vite qu’il est là. Il ajoute une nouvelle propriété à chaque élément du projet : “Run custom tool on”. Cette propriété est une collection de noms de fichiers pour lesquels l’outil personnalisé doit être exécuté à chaque fois que cet élément de projet est enregistré. Par exemple, si vous avez un template T4 (Template.tt) qui génère un fichier (Output.txt) en fonction du contenu d’un autre fichier (Input.txt), il suffit d’ajouter “Template.tt” à la propriété “Run custom tool on” de Input.txt. A chaque fois que vous enregistrerez Input.txt, l’outil personnalisé sera exécuté automatiquement sur Template.tt, ce qui regénèrera le contenu de Output.txt. Vous trouverez un exemple concret sur la page de l’outil dans la galerie Visual Studio.

J’ai créé AutoRunCustomTool il y a 6 mois environ, mais la version initiale n’était pas très bien dégrossie, donc je n’ai pas communiqué à son sujet. J’ai publié la deuxième version il y a quelques jours, et je pense qu’il est maintenant prêt à être utilisé par tout le monde. Si vous êtes intéressé par le code, vous pouvez le trouver sur GitHub, qui est aussi l’endroit où vous pouvez signaler les problèmes éventuels.

[VS 2010] Support du binding dans les InputBindings

LA fonctionnalité qui manquait à WPF !

La beta 2 de Visual Studio 2010 est là depuis quelques jours, et apporte à WPF une nouveauté que j’attendais depuis longtemps : le support du binding dans les InputBindings.

Pour rappel, le problème de la version précédente était que la propriété Command de la classe InputBinding n’était pas une DependencyProperty, on ne pouvait donc pas la définir par un binding. D’ailleurs, les InputBindings n’héritaient pas du DataContext, ce qui compliquait beaucoup les implémentations alternatives de cette fonctionnalité…

Jusqu’ici, pour lier la commande d’un KeyBinding ou MouseBinding à une propriété du DataContext, il fallait donc passer par des détours pas forcément très élégants… J’avais fini par trouver une solution acceptable, détaillée dans ce post, mais qui me laissait assez insatisfait (utilisation de la réflexion sur des membres privés, pas mal de limitations…).

J’ai découvert plus récemment le MVVM toolkit, qui propose une approche un peu plus propre : une classe CommandReference, héritée de Freezable, qui permet de mettre dans les ressources de la page ou du contrôle une référence à la commande, qu’on peut ensuite utiliser avec StaticResource. Plus propre, mais ça restait peu intuitif…

WPF 4.0 résout le problème une bonne fois pour toutes : la classe InputBinding hérite maintenant de Freezable, ce qui lui permet d’hériter du DataContext parent. De plus les propriétés Command, CommandParameter et CommandTarget sont maintenant des DependencyProperty. On peut donc laisser tomber tous les bricolages qu’il fallait utiliser jusqu’à maintenant, et aller droit au but :

    <Window.InputBindings>
        <KeyBinding Key="F5" Command="{Binding RefreshCommand}" />
    </Window.InputBindings>

Voilà qui devrait faciliter un peu le développement d’applications MVVM !

Help 3

Je change un peu de sujet pour vous parler du nouveau système de documentation offline de Visual Studio 2010, appelé “Help 3”. Il se présente comme une application web installée localement, les pages s’affichent donc dans le navigateur. Globalement, c’est beaucoup mieux… largement plus léger et plus réactif que le vieux Document Explorer livré avec les versions précédentes de Visual Studio.

En revanche, une fonction qui m’était absolument indispensable a disparu : l’index ! Il ne reste que la vue en arborescence et la recherche. Plus de trace de l’index que j’utilisais en permanence pour accéder directement à une classe ou un membre, sans forcément connaître son namespace. En plus, les résultats de la recherche ne montre pas clairement le namespace : par exemple, si vous tapez “button class” dans la recherche, pas moyen de voir la différence entre System.Windows.Forms.Button, System.Windows.Controls.Button et System.Web.UI.WebControls ! Avant, le volet “Résultats de l’index” affichait cette information clairement.

Mon sentiment sur ce nouveau système d’aide est donc un peu mitigé… il va falloir que je change ma façon d’utiliser la doc. Mais à part ce détail agaçant, c’est objectivement un gros progrès !

[Visual Studio] Astuce : définir un élément du projet comme sous-élément d’un autre

Vous avez certainement remarqué que, dans un projet C#, certains éléments sont placés sous un élément parent : c’est le cas, par exemple, pour les fichiers générés par un designer ou assistant :

Explorateur de solution
Le fichier Model1.Designer.cs est placé sous le fichier Model1.edmx

L’astuce suivante permet d’obtenir le même comportement pour vos propres fichiers.

Supposons que vous souhaitiez personnaliser les classes générées par le designer d’entités. Vous ne pouvez pas modifier le fichier Model1.Designer.cs, puisque vos modifications seraient écrasées par le designer. Vous allez donc créer un nouveau fichier, par exemple Model1.Custom.cs, où vous allez mettre votre code pour les classes d’entité (à l’aide du mot clé partial). Par défaut, ce fichier est placé directement à la racine du projet :

Explorateur de solution
Le fichier Model1.Custom.cs est placé à la racine du projet

Pour bien mettre en évidence le lien avec Model1.edmx, on préfèrerait le voir “sous” Model1.edmx… Bien que l’interface de Visual Studio ne propose pas cette option, c’est possible : il faut pour celà modifier le fichier .csproj à la main. Le plus simple, pour celà, est de décharger le projet (clic droit sur le projet, “décharger le projet“) et de l’éditer directement dans Visual Studio (clic droit sur le projet déchargé, “modifier FooBar.csproj“). Cherchez l’élément correspondant au fichier Model1.Custom.cs, et ajoutez un sous-élément comme indiqué ci-dessous :

    <Compile Include="Model1.Custom.cs">
        <DependentUpon>Model1.edmx</DependentUpon>
    </Compile>

Rechargez le projet : Model1.Custom.cs apparait maintenant sous Model1.edmx.

Explorateur de solution
Model1.Custom.cs apparait sous Model1.edmx

Cette astuce permet de mieux structurer son projet pour s’y retrouver plus facilement.