Elimina - Nozioni fondamentali su C#
Gli oggetti Discard sono variabili segnaposto intenzionalmente inutilizzate nel codice dell'applicazione. Gli oggetti discard sono equivalenti alle variabili non firmate; non hanno un valore. Un oggetto discard comunica la finalità al compilatore e ad altri utenti che leggono il codice: si intende ignorare il risultato di un'espressione. È possibile ignorare il risultato di un'espressione, uno o più membri di un'espressione di tupla, un out
parametro di un metodo o la destinazione di un'espressione corrispondente a criteri.
Le eliminazioni rendono chiara la finalità del codice. Un valore discard indica che il codice non usa mai la variabile . Migliorano la leggibilità e la manutenibilità.
Per indicare che una variabile è una variabile discard le si assegna come nome il carattere di sottolineatura (_
). Ad esempio, la chiamata al metodo seguente restituisce una tupla in cui i primi e i secondi valori vengono eliminati. area
è una variabile dichiarata in precedenza impostata sul terzo componente restituito da GetCityInformation
:
(_, _, area) = city.GetCityInformation(cityName);
A partire da C# 9.0, è possibile usare le eliminazioni per specificare i parametri di input inutilizzati di un'espressione lambda. Per altre informazioni, vedere la sezione Parametri di input di un'espressione lambda dell'articolo Espressioni lambda .
Quando _
è un'eccezione valida, il tentativo di recuperarne il valore o di usarlo in un'operazione di assegnazione genera l'errore del compilatore CS0103, "Il nome '_' non esiste nel contesto corrente". Questo errore è dovuto al fatto che _
non è assegnato un valore e potrebbe non essere nemmeno assegnato un percorso di archiviazione. Se si tratta di una variabile effettiva, non è stato possibile rimuovere più di un valore, come nell'esempio precedente.
Decostruzione di tuple e oggetti
Le eliminazioni sono utili per l'uso delle tuple quando il codice dell'applicazione usa alcuni elementi di tupla, ma ignora altri. Ad esempio, il metodo seguente QueryCityDataForYears
restituisce una tupla con il nome di una città, la relativa area, un anno, la popolazione della città per quell'anno, un secondo anno e la popolazione della città per il secondo anno. L'esempio visualizza la variazione della popolazione tra questi due anni. Tra i dati resi disponibili dalla tupla non interessa l'area della città, mentre il nome della città e le due date sono già noti in fase di progettazione. Di conseguenza interessano soltanto i due valori di popolazione archiviati nella tupla, mentre gli altri valori possono essere gestiti come variabili discard.
var (_, _, _, pop1, _, pop2) = QueryCityDataForYears("New York City", 1960, 2010);
Console.WriteLine($"Population change, 1960 to 2010: {pop2 - pop1:N0}");
static (string, double, int, int, int, int) QueryCityDataForYears(string name, int year1, int year2)
{
int population1 = 0, population2 = 0;
double area = 0;
if (name == "New York City")
{
area = 468.48;
if (year1 == 1960)
{
population1 = 7781984;
}
if (year2 == 2010)
{
population2 = 8175133;
}
return (name, area, year1, population1, year2, population2);
}
return ("", 0, 0, 0, 0, 0);
}
// The example displays the following output:
// Population change, 1960 to 2010: 393,149
Per altre informazioni sulla decostruzione di tuple con le variabili discard, vedere Decostruzione di tuple e altri tipi.
Anche il metodo Deconstruct
di una classe, struttura o interfaccia consente di recuperare e decostruire un set di dati specifico da un oggetto. È possibile usare le eliminazioni quando si è interessati a usare solo un subset dei valori deconstruiti. L'esempio seguente esegue la decostruzione di un oggetto Person
in quattro stringhe (nome, cognome, città e stato), ma rimuove il cognome e lo stato.
using System;
namespace Discards
{
public class Person
{
public string FirstName { get; set; }
public string MiddleName { get; set; }
public string LastName { get; set; }
public string City { get; set; }
public string State { get; set; }
public Person(string fname, string mname, string lname,
string cityName, string stateName)
{
FirstName = fname;
MiddleName = mname;
LastName = lname;
City = cityName;
State = stateName;
}
// Return the first and last name.
public void Deconstruct(out string fname, out string lname)
{
fname = FirstName;
lname = LastName;
}
public void Deconstruct(out string fname, out string mname, out string lname)
{
fname = FirstName;
mname = MiddleName;
lname = LastName;
}
public void Deconstruct(out string fname, out string lname,
out string city, out string state)
{
fname = FirstName;
lname = LastName;
city = City;
state = State;
}
}
class Example
{
public static void Main()
{
var p = new Person("John", "Quincy", "Adams", "Boston", "MA");
// Deconstruct the person object.
var (fName, _, city, _) = p;
Console.WriteLine($"Hello {fName} of {city}!");
// The example displays the following output:
// Hello John of Boston!
}
}
}
Per altre informazioni sulla decostruzione di tipi definiti dall'utente con le variabili discard, vedere Decostruzione di tuple e altri tipi.
Criteri di ricerca con switch
Il criterio di eliminazione può essere usato nei criteri di ricerca con l'espressione switch. Ogni espressione, inclusa null
, corrisponde sempre al criterio discard.
Nell'esempio seguente viene definito un ProvidesFormatInfo
metodo che usa un'espressione switch
per determinare se un oggetto fornisce un'implementazione IFormatProvider e verifica se l'oggetto è null
. Usa anche il criterio variabile discard per gestire gli oggetti non null di qualsiasi altro tipo.
object?[] objects = { CultureInfo.CurrentCulture,
CultureInfo.CurrentCulture.DateTimeFormat,
CultureInfo.CurrentCulture.NumberFormat,
new ArgumentException(), null };
foreach (var obj in objects)
ProvidesFormatInfo(obj);
static void ProvidesFormatInfo(object? obj) =>
Console.WriteLine(obj switch
{
IFormatProvider fmt => $"{fmt.GetType()} object",
null => "A null object reference: Its use could result in a NullReferenceException",
_ => "Some object type without format information"
});
// The example displays the following output:
// System.Globalization.CultureInfo object
// System.Globalization.DateTimeFormatInfo object
// System.Globalization.NumberFormatInfo object
// Some object type without format information
// A null object reference: Its use could result in a NullReferenceException
Chiamate ai metodi con out
parametri
Quando si chiama il metodo Deconstruct
per la decostruzione di un tipo definito dall'utente (un'istanza di una classe, una struttura o un'interfaccia), è possibile rimuovere i valori di singoli argomenti out
. Tuttavia, è anche possibile eliminare il valore degli out
argomenti quando si chiama un metodo con un out
parametro .
Nel seguente esempio viene chiamato il metodo DateTime.TryParse(String, out DateTime) per determinare se la rappresentazione stringa di una data è valida con le impostazioni cultura correnti. Dato che lo scopo dell'esempio è solo quello di convalidare la stringa di data e non quello di analizzarla per estrarre la data, l'argomento out
del metodo è una variabile discard.
string[] dateStrings = {"05/01/2018 14:57:32.8", "2018-05-01 14:57:32.8",
"2018-05-01T14:57:32.8375298-04:00", "5/01/2018",
"5/01/2018 14:57:32.80 -07:00",
"1 May 2018 2:57:32.8 PM", "16-05-2018 1:00:32 PM",
"Fri, 15 May 2018 20:10:57 GMT" };
foreach (string dateString in dateStrings)
{
if (DateTime.TryParse(dateString, out _))
Console.WriteLine($"'{dateString}': valid");
else
Console.WriteLine($"'{dateString}': invalid");
}
// The example displays output like the following:
// '05/01/2018 14:57:32.8': valid
// '2018-05-01 14:57:32.8': valid
// '2018-05-01T14:57:32.8375298-04:00': valid
// '5/01/2018': valid
// '5/01/2018 14:57:32.80 -07:00': valid
// '1 May 2018 2:57:32.8 PM': valid
// '16-05-2018 1:00:32 PM': invalid
// 'Fri, 15 May 2018 20:10:57 GMT': invalid
Una variabile discard standalone
È possibile usare una variabile discard standalone per indicare qualsiasi variabile che si è deciso di ignorare. Un uso tipico consiste nell'usare un'assegnazione per assicurarsi che un argomento non sia Null. Il codice seguente usa un'eccezione discard per forzare un'assegnazione. Il lato destro dell'assegnazione usa l'operatore di unione Null per generare un'eccezione System.ArgumentNullException quando l'argomento è null
. Il codice non richiede il risultato dell'assegnazione, quindi viene rimosso. L'espressione forza un controllo Null. L'eliminazione chiarisce la finalità: il risultato dell'assegnazione non è necessario o usato.
public static void Method(string arg)
{
_ = arg ?? throw new ArgumentNullException(paramName: nameof(arg), message: "arg can't be null");
// Do work with arg.
}
Nell'esempio seguente viene usata una variabile discard standalone per ignorare l'oggetto Task restituito da un'operazione asincrona. L'assegnazione dell'attività ha l'effetto di eliminare l'eccezione generata dall'operazione durante il completamento. Rende chiara la finalità: si vuole eliminare l'oggetto Task
e ignorare eventuali errori generati da tale operazione asincrona.
private static async Task ExecuteAsyncMethods()
{
Console.WriteLine("About to launch a task...");
_ = Task.Run(() =>
{
var iterations = 0;
for (int ctr = 0; ctr < int.MaxValue; ctr++)
iterations++;
Console.WriteLine("Completed looping operation...");
throw new InvalidOperationException();
});
await Task.Delay(5000);
Console.WriteLine("Exiting after 5 second delay");
}
// The example displays output like the following:
// About to launch a task...
// Completed looping operation...
// Exiting after 5 second delay
Senza assegnare l'attività a un'eliminazione, il codice seguente genera un avviso del compilatore:
private static async Task ExecuteAsyncMethods()
{
Console.WriteLine("About to launch a task...");
// CS4014: Because this call is not awaited, execution of the current method continues before the call is completed.
// Consider applying the 'await' operator to the result of the call.
Task.Run(() =>
{
var iterations = 0;
for (int ctr = 0; ctr < int.MaxValue; ctr++)
iterations++;
Console.WriteLine("Completed looping operation...");
throw new InvalidOperationException();
});
await Task.Delay(5000);
Console.WriteLine("Exiting after 5 second delay");
Nota
Se si esegue uno dei due esempi precedenti usando un debugger, il debugger arresterà il programma quando viene generata l'eccezione. Senza un debugger collegato, l'eccezione viene ignorata automaticamente in entrambi i casi.
_
è anche un identificatore valido. Quando viene usata fuori da un contesto supportato, _
non viene considerata come una variabile discard ma come una variabile valida. Se un identificatore con nome _
è già incluso nell'ambito, l'uso di _
come variabile discard standalone può causare:
- La modifica accidentale della variabile
_
dell'ambito, alla quale viene assegnato il valore della variabile discard prevista. Ad esempio:private static void ShowValue(int _) { byte[] arr = { 0, 0, 1, 2 }; _ = BitConverter.ToInt32(arr, 0); Console.WriteLine(_); } // The example displays the following output: // 33619968
- Un errore del compilatore per la violazione dell'indipendenza dai tipi. Ad esempio:
private static bool RoundTrips(int _) { string value = _.ToString(); int newValue = 0; _ = Int32.TryParse(value, out newValue); return _ == newValue; } // The example displays the following compiler error: // error CS0029: Cannot implicitly convert type 'bool' to 'int'
- Errore del compilatore CS0136: "Non è possibile dichiarare un parametro locale o denominato '_' in questo ambito perché tale nome viene usato in un ambito locale di inclusione per definire un parametro o locale". Per esempio:
public void DoSomething(int _) { var _ = GetValue(); // Error: cannot declare local _ when one is already in scope } // The example displays the following compiler error: // error CS0136: // A local or parameter named '_' cannot be declared in this scope // because that name is used in an enclosing local scope // to define a local or parameter