Ausschussvariablen: C#-Grundlagen
Ausschussvariablen sind Platzhaltervariablen, die im Anwendungscode bewusst nicht verwendet werden. Ausschussvariablen entsprechen nicht zugewiesenen Variablen und besitzen keinen Wert. Eine Ausschussvariable kommuniziert die Absicht an den Compiler und andere, die Ihren Code lesen: Sie wollten das Ergebnis eines Ausdrucks ignorieren. Möglicherweise möchten Sie das Ergebnis eines Ausdrucks, einen oder mehrere Member eines Tupelausdrucks, einen out
-Parameter zu einer Methode oder das Ziel eines Musterabgleichausdrucks ignorieren.
Ausschussvariablen machen den Zweck Ihres Codes deutlich. Eine Ausschussvariable gibt an, dass die Variable in unserem Code niemals verwendet. Sie verbessern seine Lesbarkeit und Verwaltbarkeit.
Sie geben an, dass es sich bei einer Variable um Ausschuss handelt, indem Sie ihr einen Unterstrich (_
) als Namen zuweisen. Der folgende Methodenaufruf gibt beispielsweise einen Tupel zurück, in dem der erste und der zweite Wert Ausschussvariablen sind. area
ist eine zuvor deklarierte Variable, die auf die dritte von GetCityInformation
zurückgegebene Komponente festgelegt wurde:
(_, _, area) = city.GetCityInformation(cityName);
Sie können mithilfe von Ausschussvariablen nicht verwendete Eingabeparameter eines Lambdaausdrucks angeben. Weitere Informationen finden Sie im Abschnitt Eingabeparameter eines Lambdaausdrucks des Artikels Lambdaausdrücke.
Wenn es sich bei _
um eine gültige Ausschussvariable handelt, wird beim Versuch, ihren Wert abzurufen oder sie in einem Zuweisungsvorgang zu verwenden, der Compilerfehler CS0103, „Der Name ‚_‘ existiert im aktuellen Kontext nicht“, generiert. Die Ursache dieses Fehlers ist, dass _
kein Wert und möglicherweise nicht einmal ein Speicherort zugewiesen ist. Wenn es sich dabei um eine tatsächliche Variable handeln würde, könnten Sie nicht wie im vorherigen Beispiel mehr als einen Wert verwerfen.
Dekonstruieren von Tupeln und Objekten
Ausschussvariablen sind nützlich, wenn mit Tupeln gearbeitet wird und Ihr Anwendungscode einige Elemente des Tupels verwendet, andere aber ignoriert. Die folgende Methode QueryCityDataForYears
gibt beispielsweise einen Tupel mit dem Namen einer Stadt, ihrer Fläche, einer Jahreszahl, der Bevölkerung der Stadt in diesem Jahr, einer zweiten Jahreszahl und der Bevölkerung der Stadt im zweiten Jahr zurück. Das Beispiel zeigt die Veränderung der Bevölkerung zwischen diesen beiden Jahren. Von den Daten, die im Tupel verfügbar sind, ist die Fläche der Stadt nicht relevant für uns und außerdem kennen wir den Namen der Stadt und die zwei Datumswerte zur Entwurfszeit. Darum sind wir nur an den zwei Bevölkerungsgwerten interessiert, die im Tupel gespeichert sind und behandeln die restlichen Werte als Ausschuss.
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
Weitere Informationen zum Dekonstruieren von Tupeln mit Ausschüssen finden Sie unter Deconstructing tuples and other types (Dekonstruieren von Tupeln und anderen Typen).
Die Methode Deconstruct
einer Klasse, Struktur oder Schnittstelle ermöglicht es Ihnen ebenfalls, einen bestimmten Satz von Daten aus einem Objekt abzurufen und zu dekonstruieren. Sie können Ausschussvariablen verwenden, wenn Sie nur mit einer Teilmenge der dekonstruierten Werte arbeiten möchten. Im folgenden Beispiel wird ein Person
-Objekt in vier Zeichenfolgen (Vor- und Nachname, Ort und Staat) dekonstruiert, Nachname und Staat werden jedoch verworfen.
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!
}
}
}
Weitere Informationen zum Dekonstruieren von benutzerdefinierten Typen mit Ausschüssen finden Sie unter Deconstructing tuples and other types (Dekonstruieren von Tupeln und anderen Typen).
Musterabgleich mit switch
Das Ausschussmuster kann beim Musterabgleich mit dem Schlüsselwort switch expression verwendet werden. Jeder Ausdruck, einschließlich null
, stimmt immer mit dem Ausschussmuster überein.
Im folgenden Beispiel wird die Methode ProvidesFormatInfo
definiert, der switch
-Ausdruck verwendet, um zu bestimmen, ob ein Objekt eine IFormatProvider-Implementierung bereitstellt und überprüft, ob das Objekt null
ist. Sie verwendet auch das Ausschussmuster, um Nicht-NULL-Objekte eines anderen Typs zu verarbeiten.
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
Aufrufe von Methoden mit out
-Parametern
Wenn die Methode Deconstruct
aufgerufen wird, um einen benutzerdefinierten Typ (eine Instanz einer Klasse, Struktur oder Schnittstelle) zu dekonstruieren, können Sie die Werte einzelner out
-Argumente verwerfen. Sie können ebenfalls den Wert von out
-Argumenten verwerfen, wenn Sie eine Methode mit einem out
-Parameter aufrufen.
Im folgenden Beispiel wird die Methode DateTime.TryParse(String, out DateTime) aufgerufen, um zu bestimmen, ob die Zeichenfolgendarstellung eines Datums in der aktuellen Kultur gültig ist. Da das Beispiel darauf beschränkt ist, die Datumszeichenfolge zu überprüfen und diese nicht analysiert wird, um das Datum zu extrahieren, ist das Argument out
der Methode ein Ausschuss.
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
Ein eigenständiger Ausschuss
Sie können einen eigenständigen Ausschuss verwenden, um eine beliebige Variable anzugeben, die Sie ignorieren möchten. Eine typische Verwendung ist, mit einer Zuweisung sicherzustellen, dass ein Argument nicht NULL ist. Im folgenden Code wird mit einer Ausschussvariablen eine Zuweisung erzwungen. Auf der rechten Seite der Zuweisung wird mit dem NULL-Sammeloperator eine System.ArgumentNullException ausgelöst, wenn das Argument null
ist. Der Code benötigt nicht das Ergebnis der Zuweisung und wird daher verworfen. Der Ausdruck erzwingt eine NULL-Überprüfung. Die Ausschussvariable verdeutlicht Ihre Absicht: Das Ergebnis der Zuweisung ist nicht erforderlich oder wird nicht verwendet.
public static void Method(string arg)
{
_ = arg ?? throw new ArgumentNullException(paramName: nameof(arg), message: "arg can't be null");
// Do work with arg.
}
Im folgenden Beispiel wird ein eigenständiger Ausschuss verwendet, um das Objekt Task zu ignorieren, das von einem asynchronen Vorgang zurückgegeben wird. Die Zuweisung der Aufgabe unterdrückt die Ausnahme, die vom Vorgang kurz vor dem Abschluss ausgelöst wird. Es verdeutlicht Ihre Absicht: Sie möchten den Task
verwerfen und alle Fehler ignorieren, die von diesem asynchronen Vorgang generiert wurden.
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
Ohne Zuweisung der Aufgabe zu einer Ausschussvariablen generiert der folgende Code eine Compilerwarnung:
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");
Hinweis
Wenn Sie eines der beiden vorherigen Beispiele mithilfe eines Debuggers ausführen, wird das Programm vom Debugger angehalten, wenn die Ausnahme ausgelöst wird. Wenn kein Debugger angefügt ist, wird die Ausnahme in beiden Fällen ignoriert.
_
ist auch ein gültiger Bezeichner. Bei der Verwendung außerhalb eines unterstützten Kontexts wird _
nicht als Ausschuss, sondern als gültige Variable behandelt. Wenn bereits ein Bezeichner mit dem Namen _
im Bereich vorhanden ist, kann die Verwendung von _
als eigenständiger Ausschuss zu Folgendem führen:
- Versehentliche Änderung des Werts der im Bereich befindlichen Variable
_
, indem dieser der Wert eines beabsichtigten Ausschusses zugewiesen wird. Beispiel:private static void ShowValue(int _) { byte[] arr = [0, 0, 1, 2]; _ = BitConverter.ToInt32(arr, 0); Console.WriteLine(_); } // The example displays the following output: // 33619968
- Ein Compilerfehler wegen Verletzung der Typsicherheit. Beispiel:
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'
- Compilerfehler CS0136: „Eine lokale Variable oder ein Parameter mit dem Namen „_“ kann in diesem Bereich nicht deklariert werden, da der Name in einem einschließenden lokalen Bereich verwendet wird, um eine lokale Variable oder einen Parameter zu definieren.“ Beispiel:
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