Global ed implicit usings

In questo periodo sono stato un po' assente per via di vari impegni di lavoro e non sono riuscito a scrivere prima una serie di post che avevo in programma sulla nuova versione di .NET, la 6, che Microsoft ha rilasciato lo scorso novembre.

Questa versione, oltre ad essere importante perché è una release LTS, ovvero che sarà supportata per più tempo rispetto alle altre versioni, introduce anche una serie di novità al linguaggio C# (giunto alla versione 10) che aiutano noi sviluppatori a scrivere codice più compatto ed ordinato.

Global usings

Fin dalla prima versione di C#, l'istruzione using ci ha permesso di semplificare la scrittura del nostro codice facendo sì di poter scrivere le istruzioni in maniera "abbreviata", senza il bisogno di dover digitare tutte le volte per intero i nomi degli oggetti (che siano classi, strutture, pagine, form, ecc.) comprensivi di namespace. Fino alla versione 9 di C# però, la dichiarazione dei namespace, era relativa al singolo file .cs e, specialmente nei grandi progetti, questo può generare un po' di confusione dal punto di vista della manutenibilità. Infatti, in ogni file bisogna sempre ripetere la dichiarazione di tutti i namespace necessari (vi lascio immaginare un progetto composto da molti file .cs dove magari ad un certo punto si deve modificare o rimuovere la dichiarazione di uno o più namespace).

Con C# 10 però le cose sono cambiate, Microsoft ha introdotto la possibilità di dichiarare i namespace a livello globale, in modo da dichinare i namespace una volta e rendere accessibili a tutta l'applicazione. Sfruttare questa nuova caratteristica è molto semplice, basta solo anteporre global alla dichiarazione del namespace. Vediamo un esempio.

global using System;
global using System.IO;

In questo mood abbiamo reso gli oggetti dei namespace System e System.IO accessibili in tutta l'applicazione.

So che qualcuno di voi starà pensando che questo, se usato male, potrebbe portare ad avere del codice un po' più complesso da leggere e, purtroppo, è vero. Dobbiamo quindi prestare un po' di attenzione su come scriviamo in nostro codice. Io come "best practices" creo un file GlobalUsings.cs al cui interno vado a dichiarare solamente i namespace maggiormente utilizzati, mentre quelli che sono specifici di un oggetto continuo a dichiararli nel file nel modo classico.

Le novità però non si limitano qui, possiamo sfruttare infatti il global anche con gli alias e gli using statici. Vediamo un altro esempio.

global using static System.Console;
global using static System.Math;

File-scoped namespace

Restando sempre in tema di namespace, un'altra caratteristica di C# 10 che ci permette di scrivere del codice meno verboso è il File-scoped namespace.

Esempio Hello world in C# 9

using System;

namespace Santoni1981.HelloWorldCSharpDemo
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Hello World");
        }
    }
}

Esempio Hello world in C# 10

using System;

namespace Santoni1981.HelloWorldCSharpDemo;

class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine("Hello World");
    }
}

Come possiamo osservare, nella versione C# 10, il codice della classe è "meno indentato" rispetto alla quella C# 9. Questa a prima vista potrebbe sembrare qualcosa di poco utile, ma usata insieme ai Top-level statements e gli Implicit usings (che vedremo più avanti nel corso di questo articolo) possiamo scrivere le nostre applicazioni in modo molto compatto.

Implicit usings

Con .NET 6 è anche stato introdotto l'uso degli Implicit usings. Di default, per i nuovi progetti, il compilatore provvede a dichiarare implicitamente (e a livello globale) alcuni namespace più utilizzati, in base all'SDK indicato nel file .csproj. Questo significa che, ad esempio per un progetto di tipo Console, senza che ci sia bisogno di dichiarare l'uso del namespace System, sarà possibile scrivere Console.WriteLine("Hello world"); senza ricevere errori di compilazione.

Per abilitare gli Implicit usings nei nostri progetti è sufficiente aprire il file .csproj ed aggiungere la proprietà ImplicitUsings a enable.

<PropertyGroup>
    <!-- Altre proprietà -->
    <ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>

Di seguito la tabella copiata dalla documentazione sul sito di Microsoft che indica per ogni SDK gli using impliciti

SDK Namespace impliciti
Microsoft.NET.Sdk SystemSystem.Collections.GenericSystem.IOSystem.LinqSystem.Net.HttpSystem.ThreadingSystem.Threading.Tasks
Microsoft.NET.Sdk.Web System.Net.Http.JsonMicrosoft.AspNetCore.BuilderMicrosoft.AspNetCore.HostingMicrosoft.AspNetCore.HttpMicrosoft.AspNetCore.RoutingMicrosoft.Extensions.ConfigurationMicrosoft.Extensions.DependencyInjectionMicrosoft.Extensions.HostingMicrosoft.Extensions.Logging
Microsoft.NET.Sdk.Worker Microsoft.Extensions.ConfigurationMicrosoft.Extensions.DependencyInjectionMicrosoft.Extensions.HostingMicrosoft.Extensions.Logging
Microsoft.NET.Sdk.WindowsDesktop (Windows Forms) Tutti i namespace di Microsoft.NET.SdkSystem.DrawingSystem.Windows.Forms
Microsoft.NET.Sdk.WindowsDesktop (WPF) Tutti i namespace di Microsoft.NET.Sdk eccetto System.IO e System.Net.Http

È anche possibile aggiungere o rimuovere degli using se necessario intervenendo sempre sul file .csproj.

Per aggiungere uno using che non presente tra quelli predefinì del SDK:

<ItemGroup>
  <Using Include="System.IO.Pipes" />
</ItemGroup>

Mentre per rimuovere uno using da quelli predefiniti del SDK:

<ItemGroup>
  <Using Remove="System.Threading.Tasks" />
</ItemGroup>

Conclusioni

Ovviamente le novità della nuova versione di .NET non si fermato qui, nei prossimi giorni scriverò altri articoli a riguardo. Nel frattempo, mi piacerebbe sapere voi cosa ne pensate di queste caratteristiche che puntano a far scrivere codice più compatto a noi programmatori. Se vi va scrivetemelo nei commenti.

18