用户定义类型的扩展方法
如果没有创建类、结构或接口,仍可通过实现一个或多个 Deconstruct 扩展方法来析构该类型的对象,以返回所需值。
以下示例为 System.Reflection.PropertyInfo 类定义了两个 Deconstruct 扩展方法。 第一个方法返回一组值,指示属性的特征,包括其类型、是静态还是实例、是否为只读,以及是否已编制索引。 第二个方法指示属性的可访问性。 因为 get 和 set 访问器的可访问性可能不同,所以布尔值指示属性是否具有单独的 get 和 set 访问器,如果是,则指示它们是否具有相同的可访问性。 如果只有一个访问器,或者 get 和 set 访问器具有相同的可访问性,则 access 变量指示整个属性的可访问性。 否则,get 和 set 访问器的可访问性由 getAccess 和 setAccess 变量指示。
using System;
using System.Collections.Generic;
using System.Reflection;
public static class ReflectionExtensions
{
public static void Deconstruct(this PropertyInfo p, out bool isStatic,
out bool isReadOnly, out bool isIndexed,
out Type propertyType)
{
var getter = p.GetMethod;
// Is the property read-only?
isReadOnly = ! p.CanWrite;
// Is the property instance or static?
isStatic = getter.IsStatic;
// Is the property indexed?
isIndexed = p.GetIndexParameters().Length > 0;
// Get the property type.
propertyType = p.PropertyType;
}
public static void Deconstruct(this PropertyInfo p, out bool hasGetAndSet,
out bool sameAccess, out string access,
out string getAccess, out string setAccess)
{
hasGetAndSet = sameAccess = false;
string getAccessTemp = null;
string setAccessTemp = null;
MethodInfo getter = null;
if (p.CanRead)
getter = p.GetMethod;
MethodInfo setter = null;
if (p.CanWrite)
setter = p.SetMethod;
if (setter != null && getter != null)
hasGetAndSet = true;
if (getter != null)
{
if (getter.IsPublic)
getAccessTemp = "public";
else if (getter.IsPrivate)
getAccessTemp = "private";
else if (getter.IsAssembly)
getAccessTemp = "internal";
else if (getter.IsFamily)
getAccessTemp = "protected";
else if (getter.IsFamilyOrAssembly)
getAccessTemp = "protected internal";
}
if (setter != null)
{
if (setter.IsPublic)
setAccessTemp = "public";
else if (setter.IsPrivate)
setAccessTemp = "private";
else if (setter.IsAssembly)
setAccessTemp = "internal";
else if (setter.IsFamily)
setAccessTemp = "protected";
else if (setter.IsFamilyOrAssembly)
setAccessTemp = "protected internal";
}
// Are the accessibility of the getter and setter the same?
if (setAccessTemp == getAccessTemp)
{
sameAccess = true;
access = getAccessTemp;
getAccess = setAccess = String.Empty;
}
else
{
access = null;
getAccess = getAccessTemp;
setAccess = setAccessTemp;
}
}
}
public class ExampleExtension
{
public static void Main()
{
Type dateType = typeof(DateTime);
PropertyInfo prop = dateType.GetProperty("Now");
var (isStatic, isRO, isIndexed, propType) = prop;
Console.WriteLine($"\nThe {dateType.FullName}.{prop.Name} property:");
Console.WriteLine($" PropertyType: {propType.Name}");
Console.WriteLine($" Static: {isStatic}");
Console.WriteLine($" Read-only: {isRO}");
Console.WriteLine($" Indexed: {isIndexed}");
Type listType = typeof(List<>);
prop = listType.GetProperty("Item",
BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static);
var (hasGetAndSet, sameAccess, accessibility, getAccessibility, setAccessibility) = prop;
Console.Write($"\nAccessibility of the {listType.FullName}.{prop.Name} property: ");
if (!hasGetAndSet | sameAccess)
{
Console.WriteLine(accessibility);
}
else
{
Console.WriteLine($"\n The get accessor: {getAccessibility}");
Console.WriteLine($" The set accessor: {setAccessibility}");
}
}
}
// The example displays the following output:
// The System.DateTime.Now property:
// PropertyType: DateTime
// Static: True
// Read-only: True
// Indexed: False
//
// Accessibility of the System.Collections.Generic.List`1.Item property: public
系统类型的扩展方法
为了方便起见,某些系统类型提供 Deconstruct 方法。 例如,System.Collections.Generic.KeyValuePair<TKey,TValue> 类型提供此功能。 循环访问 System.Collections.Generic.Dictionary<TKey,TValue> 时,每个元素都是 KeyValuePair<TKey, TValue>,并且可以析构。 请考虑以下示例:
Dictionary<string, int> snapshotCommitMap = new(StringComparer.OrdinalIgnoreCase)
{
["https://github.com/dotnet/docs"] = 16_465,
["https://github.com/dotnet/runtime"] = 114_223,
["https://github.com/dotnet/installer"] = 22_436,
["https://github.com/dotnet/roslyn"] = 79_484,
["https://github.com/dotnet/aspnetcore"] = 48_386
};
foreach (var (repo, commitCount) in snapshotCommitMap)
{
Console.WriteLine(
$"The {repo} repository had {commitCount:N0} commits as of November 10th, 2021.");
}
向要没有 Deconstruct 方法的系统类型添加此方法。 请考虑以下扩展方法:
public static class NullableExtensions
{
public static void Deconstruct<T>(
this T? nullable,
out bool hasValue,
out T value) where T : struct
{
hasValue = nullable.HasValue;
value = nullable.GetValueOrDefault();
}
}
通过此扩展方法,可将所有 Nullable<T> 类型析构为 (bool hasValue, T value) 的元组。 下面的示例演示了使用此扩展方法的代码:
DateTime? questionableDateTime = default;
var (hasValue, value) = questionableDateTime;
Console.WriteLine(
$"{
{ HasValue = {hasValue}, Value = {value} }}");
questionableDateTime = DateTime.Now;
(hasValue, value) = questionableDateTime;
Console.WriteLine(
$"{
{ HasValue = {hasValue}, Value = {value} }}");
// Example outputs:
// { HasValue = False, Value = 1/1/0001 12:00:00 AM }
// { HasValue = True, Value = 11/10/2021 6:11:45 PM }
record 类型
使用两个或多个位置参数声明记录类型时,编译器将为 record 声明中的每个位置参数创建一个带有 out 参数的 Deconstruct 方法。