in C#, Informatica, Programmazione

C# 7.0 – Variabili Discard

A partire da c# 7, C# ci mette a disposizione le variabili discard. Si tratta in pratica di variabili non utilizzate, fittizie (dummy) che non verranno utilizzate all’interno del codice.

La variabile discard viene definita utilizzando il carattere _ .

Supponiamo di voler fare un operazione di add tra due numeri:

_ = 1 + 1;

questa operazione assegna il risultato della somma all’interno dell’operatore discard (_).

L’assegnazione al discard, non consente di accedere al risultato dell’operazione. Se, ad esempio, volessimo stampare il risulato dell’assegnazione il compilatore generebbe un errore.

Vediamo quindi alcuni esempi di quando è possibile utilizzare discard:

  • Discard in metodi con parametri di tipo out
  • Discard con tuple
  • Verifica di oggetti null
  • Utilizzo all’interno di Task
  • Pattern matching con switch

Discard in metodi con parametri di tipo out

Supponiamo di utilizzare int.TryParse per poter verificare se una stringa può essere convertita in int oppure no. Quello che si fa normalmente è qualcosa del genere:

bool isValidInt = int.TryParse(stringa, out int parsedInt);

e successivamente si procede con la verifica di isValidInt per capire se è stato possibile convertire da stringa a int, ottenendo il risultato all’interno della variabile parsedInt.

Nel caso in cui non ci interessi il valore presente in parsedInt, ma solo capire se è possibile o meno effettuare la parsificazione si può utilizzare il discard :

if (int.TryParse(stringa, out _))

in questo modo non viene allocata una nuova variabile come risultato dell’operazione, dal momento che siamo interessati solo a capire se è possibile o meno.

Nel caso precedente si può anche utilizzare il discard all’interno di una expression bodied:

static bool IsInt(string stringa) => int.TryParse(stringa, out _);

risultando sicuramente molto piu leggibile

Discard con tuple

Quando utilizziamo le tuple non sempre siamo interessati a tutti i loro valori. Supponiamo di avere una funzione statica che genera una tupla:

static (string nome, string cognome) GenerateDefaultPerson()
{
    return ("Nome", "Cognome");
}

e di essere interessati soltanto al cognome. La funzione precedente può essere richiamata nel seguente modo:

var (_, cognome) = GenerateDefaultPerson();

indicato con un discard la variabile che non ci interessa gestire.

Verifica di oggetti null

Per la verifica di oggetti null possiamo procedere in diversi modi. Il più semplice è sicuramente quello di utilizzare una if, generando un’eccezzione nel caso in cui ci troviamo a che fare con un oggetto a null:

private static void CheckIfMessageIsNull(string message)
{
    if (message is null)
    {
        throw new ArgumentNullException(nameof(message));
    }
    WriteLine(message);
}

il codice preceden può essere semplificato (e reso più leggibile) nel seguente modo :

 string checkedMessage = message ?? throw new ArgumentNullException(nameof(message));

Come si può notare la sintassi è stata decisamente migliorata e compattata all’interno di un’unica riga.

Ma fino a questo momento non si è ancora utilizzato il discard.

Quello che si può notare è che viene associato a checkedMessage il valore di message, e nel caso in cui il valore sia null viene lanciata un’eccezione.

Qunindi checkedMessage è una variabile di fatto che non serve. Possiamo riscrivere il codice nel seguente modo:

_ = message ?? throw new ArgumentNullException(nameof(message));

A livello codice non è cambiato nulla, ma abbiamo risparmiato una variabile introducendo il discard.

Utilizzo all’interno di Task

Supponiamo di avere la un Task.Run del tipo:

Task.Run(() => MyFunction());

dove la funzione MyFunction è una semplice funzione statica che stampa a video “Hello World!”:

private static string MyFunction()
{
    string greeting = "Hello World!";
    return greeting;
}

quelloc che verrà segnalato dal compilatore sarà un warning CS1998  :

// Warning CS1998  This async method lacks ‘await’ operators and will run synchronously.

che sta ad indicare che non è stato utilizzato await all’interno nella Task.run().

Se vogliamo evitare il warning da parte del compilatore possiamo associare il risultato della Task.Run ad un discard:

_ = Task.Run(() => MyFunction());

ovviamente questo comporta la soppressione del warning da parte del compilatore, ma anche delle eventuali eccezioni che verrebbero generate (non me ne vogliano i puristi del codice!).

Pattern Matching con Switch

All’interno del pattern matching è possibile utilizzare il discard per raccogliere tutto quello che non è definito, come nell’esempio:

private static void FunzioneSwitch(object o)
{
    switch (o)
    {
        case null:
            WriteLine("o is null");
            break;
        case string s:
            WriteLine($"s.ToUpperInvariant()");
            break;
        case var _:
            WriteLine($"{o.GetType()}");
            break;
    }
}