弃元是一种在应用程序代码中人为取消使用的占位符变量。 弃元相当于未赋值的变量;它们没有值。 弃元将意图传达给编译器和其他读取代码的文件:你打算忽略表达式的结果。 你可能希望忽略表达式的结果、元组表达式的一个或多个成员、 out
方法的参数或模式匹配表达式的目标。
弃元使代码意图更加明确。 丢弃意味着我们的代码永远不会使用该变量。 它们增强了其可读性和可维护性。
你通过为变量分配下划线(_
)作为其名称来指示它是弃用变量。 例如,以下方法调用返回一个元组,其中第一个值和第二个值为弃元。
area
是以前声明的变量,设置为由 GetCityInformation
返回的第三个组件:
(_, _, area) = city.GetCityInformation(cityName);
可以使用忽略来标识 lambda 表达式中未使用的输入参数。 有关详细信息,请参阅 Lambda 表达式文章的 lambda 表达式部分的输入参数。
当 _
是有效的丢弃时,尝试检索其值或在赋值操作中使用它会生成编译器错误 CS0103,“当前上下文中不存在名称'_'”。 此错误是因为 _
未分配值,甚至可能未分配存储位置。 如果它是实际变量,则无法放弃多个值,如前面的示例所示。
元组和对象析构
如果应用程序代码使用某些元组元素,但忽略其他元素,这时使用弃元来处理元组就会很有用。 例如,下面的 QueryCityDataForYears
方法返回一个元组,其名称为城市、其区域、一年、该年份的城市人口、第二年,以及该城市的第二年的人口。 该示例显示了两个年份之间人口的变化。 对于元组提供的数据,我们不关注城市面积,并在一开始就知道城市名称和两个日期。 因此,我们只关注存储在元组中的两个人口数量值,可将其余值作为占位符处理。
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
有关使用占位符析构元组的详细信息,请参阅 析构元组和其他类型。
类 Deconstruct
、结构或接口的方法还允许检索和解构对象中的特定数据集。 如果想只使用析构值的一个子集,可使用弃元。 以下示例将对象解构为四个 Person
字符串(名字和姓氏、城市和州),但放弃姓氏和州。
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!
}
}
}
有关使用弃元析构用户定义的类型的详细信息,请参阅 析构元组和其他类型。
利用 switch
的模式匹配
弃元模式可通过 switch 表达式用于模式匹配。 每个表达式(包括 null
)始终匹配丢弃模式。
下面的示例定义一个 ProvidesFormatInfo
方法,该方法使用 switch
表达式来确定对象是否提供 IFormatProvider 实现并测试对象是否为 null
。 它还使用占位符模式来处理任何其他类型的非 null 对象。
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
对具有 out
参数的方法的调用
调用 Deconstruct
该方法以解构用户定义的类型(类、结构或接口的实例)时,可以放弃各个 out
参数的值。 但是,在调用任何带有out
参数的方法时,也可以放弃out
参数的值。
以下示例调用 DateTime.TryParse(String, out DateTime) 方法以确定日期的字符串形式在当前文化中是否有效。 因为此示例仅涉及验证日期字符串,而不是分析它以提取日期,因此该方法中的 out
参数是一个弃元。
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
独立弃元
可使用独立弃元来指示要忽略的任何变量。 一个典型的用途是使用赋值来确保参数不为 null。 下面的代码使用弃元来强制赋值。 赋值的右侧使用 Null 合并操作符,用于在参数为 System.ArgumentNullException 时引发 null
。 代码不需要赋值的结果,因此会被丢弃。 表达式强制执行空值检查。 丢弃操作阐明了你的意图:分配结果不需要或不会被使用。
public static void Method(string arg)
{
_ = arg ?? throw new ArgumentNullException(paramName: nameof(arg), message: "arg can't be null");
// Do work with arg.
}
以下示例使用独立占位符来忽略异步操作返回的 Task 对象。 分配任务的效果等同于抑制操作即将完成时所引发的异常。 它使你的意图清晰:你想要丢弃Task
,并忽略从该异步操作中产生的任何错误。
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
如果不将任务分配给弃元,则以下代码会生成编译器警告:
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");
注释
如果使用调试器运行上述两个示例之一,调试器将在引发异常时停止程序。 如果没有附加调试器,则在这两种情况下都以无提示方式忽略异常。
_
也是有效的标识符。 在受支持的上下文之外使用时, _
不会被视为放弃,而是被视为有效的变量。 如果名为 _
的标识符已在范围内,则使用 _
作为独立占位符可能导致:
- 将预期的占位符的值赋给范围内
_
变量,会导致该变量的值被意外修改。 例如:private static void ShowValue(int _) { byte[] arr = [0, 0, 1, 2]; _ = BitConverter.ToInt32(arr, 0); Console.WriteLine(_); } // The example displays the following output: // 33619968
- 由于违反类型安全而产生的编译错误。 例如:
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'