10 个转换
10.1 常规
转换会导致表达式转换为特定类型或被视为特定类型的表达式;在前例中,转换可能涉及表示形式的更改。 转换可以是 隐式 转换, 也可以是显式转换,这确定是否需要显式强制转换。
例如:例如,从类型到类型的
int
long
转换是隐式的,因此可以隐式地将类型的int
表达式视为类型long
。 从类型long
到类型int
的相反转换是显式的,因此需要显式强制转换。int a = 123; long b = a; // implicit conversion from int to long int c = (int) b; // explicit conversion from long to int
end 示例
某些转换由语言定义。 程序还可以定义自己的转换(§10.5)。
语言中的某些转换是从表达式到类型定义的,另一些转换是从类型到类型。 从类型转换适用于具有该类型的所有表达式。
示例:
enum Color { Red, Blue, Green } // The expression 0 converts implicitly to enum types Color c0 = 0; // Other int expressions need explicit conversion Color c1 = (Color)1; // Conversion from null expression (no type) to string string x = null; // Conversion from lambda expression to delegate type Func<int, int> square = x => x * x;
end 示例
10.2 隐式转换
10.2.1 常规
以下转换被归类为隐式转换:
- 标识转换 (§10.2.2)
- 隐式数值转换 (§10.2.3)
- 隐式枚举转换 (§10.2.4)
- 隐式内插字符串转换 (§10.2.5)
- 隐式引用转换 (§10.2.8)
- 装箱转换 (§10.2.9)
- 隐式动态转换(§10.2.10)
- 隐式类型参数转换 (§10.2.12)
- 隐式常量表达式转换 (§10.2.11)
- 用户定义的(包括提升)隐式转换(§10.2.14)
- 匿名函数转换 (§10.2.15)
- 方法组转换 (§10.2.15)
- Null 文本转换 (§10.2.7)
- 隐式可为 null 转换 (§10.2.6)
- 隐式元组转换 (§10.2.13)
- 默认文本转换 (§10.2.16)
- 隐式引发转换 (§10.2.17)
隐式转换可能发生在各种情况下,包括函数成员调用(§12.6.6)、强制转换表达式(§12.9.7)和赋值(§12.21)。
预定义的隐式转换始终成功,永远不会引发异常。
注意:正确设计的用户定义的隐式转换也应表现出这些特征。 end note
出于转换目的,类型和object
dynamic
标识可转换(§10.2.2)。
但是,动态转换(§10.2.10)仅适用于类型 dynamic
(§8.2.4)的表达式。
10.2.2 标识转换
标识转换从任何类型转换为同一类型或运行时等效的类型。 这种转换存在的原因之一是,可以 T
说类型的类型或表达式 T
可以转换为 T
自身。 存在以下标识转换:
- 对于任何类型
T
,介于T
和T
之间。 - 介于和引用类型之间
T
和T?
用于任何引用类型T
。 - 介于
object
和dynamic
. - 当每个对应元素类型对之间存在标识转换时,在具有相同的 arity 的所有元组类型与相应的构造
ValueTuple<...>
类型之间存在标识转换。 - 在从同一泛型类型构造的类型之间,每个对应的类型自变量之间存在标识转换。
示例:下面说明了第三个规则的递归性质:
(int a , string b) t1 = (1, "two"); (int c, string d) t2 = (3, "four"); // Identity conversions exist between // the types of t1, t2, and t3. var t3 = (5, "six"); t3 = t2; t2 = t1; var t4 = (t1, 7); var t5 = (t2, 8); // Identity conversions exist between // the types of t4, t5, and t6. var t6 =((8, "eight"), 9); t6 = t5; t5 = t4;
元组
t1
的类型,t2
并且t3
都有两个int
元素:后跟一个string
。 元组元素类型可能由元组本身,如中t4
和t5
t6
。 每个对应的元素类型(包括嵌套元组)之间存在标识转换,因此元组t4
类型之间存在标识转换,t5
以及t6
。end 示例
所有标识转换都是对称的。 如果标识转换存在自T₁
到T₂
,则标识转换存在自该转换T₂
。T₁
当标识转换存在于两种类型之间时,这两种类型是 可转换 的标识。
在大多数情况下,标识转换在运行时不起作用。 但是,由于浮点运算的精度可能高于其类型(§8.3.7)规定的精度,因此其结果赋值可能会导致精度损失,并且可以保证显式强制转换将精度降低到类型规定的精度(§12.9.7)。
10.2.3 隐式数值转换
隐式数值转换包括:
- 从
sbyte
到、int
、long
、float
、double
或decimal
。short
- 从
byte
、ushort
、、int
uint
、long
、ulong
、float
、或double
decimal
。short
- 从
short
到int
、long
、float
、double
或decimal
。 - 从
ushort
、uint
、long
、ulong
、float
、或double
decimal
。int
- From
int
tolong
、float
,double
or.decimal
- 从
uint
到long
、ulong
、float
、double
或decimal
。 - 从
long
到float
,double
或decimal
。 - 从
ulong
到float
,double
或decimal
。 - 从
char
、ushort
int
、uint
、long
、ulong
、float
、或double
decimal
。 - 从
float
到double
。
从int
、 long
uint
或ulong
转换到或从double
long
中ulong
转换float
可能会导致精度损失,但永远不会造成数量级损失。 其他隐式数值转换不会丢失任何信息。
没有对类型的预定义隐式转换 char
,因此其他整型类型的值不会自动转换为类型 char
。
10.2.4 隐式枚举转换
隐式枚举转换允许具有任何整数类型的constant_expression(§12.23)和值零转换为任何enum_type,以及任何基础类型为enum_type的nullable_value_type。 在后一种情况下,转换通过转换为基础 enum_type 并包装结果(§8.3.12)进行评估。
10.2.5 隐式内插字符串转换
隐式内插字符串转换允许转换为或System.FormattableString
实现System.IFormattable
interpolated_string_expression(§12.8.3System.IFormattable
)。
应用此转换时,不会从内插字符串构成字符串值。 而是创建实例System.FormattableString
,如 §12.8.3 中所述。
10.2.6 隐式可为 null 转换
隐式可为 null 转换是从隐式预定义转换派生的那些可为 null 的转换(§10.6.1)。
10.2.7 Null 文本转换
从文本到任何引用类型或可以为 null 的值类型存在 null
隐式转换。 如果目标类型为引用类型或给定可为 null 值类型的 null 值(§8.3.12),则此转换将生成 null 引用。
10.2.8 隐式引用转换
隐式引用转换包括:
- 从任何 reference_type 到
object
和dynamic
。 - 从任何class_type
S
到任何class_typeT
,提供的S
派生自T
。 - 从任何class_type
S
到任何interface_typeT
,都提供了S
实现T
。 - 从任何interface_type
S
到任何interface_typeT
,提供的S
派生自T
。 - 从具有元素类型的array_type
S
到具有元素类型的Sᵢ
Tᵢ
array_typeT
,前提是以下所有内容均为 true:S
并且T
仅在元素类型中不同。 换句话说,S
维度T
数相同。- 隐式引用转换从 <
a0/> 到 Tᵢ
.
- 从单维数组类型
S[]
到System.Collections.Generic.IReadOnlyList<T>
System.Collections.Generic.IList<T>
基接口,前提是从隐式标识或引用转换S
到T
。 - 从任何 array_type 到
System.Array
它实现的接口。 - 从任何 delegate_type 到
System.Delegate
它实现的接口。 - 从 null 文本(§6.4.5.7)到任何引用类型。
T
如果reference_type具有隐式标识或对reference_type的引用转换,并且T₀
标识转换到T
reference_type。T₀
- 如果接口或委托类型具有隐式标识或引用转换到接口或委托类型并且可转换方差(§18.2.3.3.3),则从任何reference_type到接口或委托类型
T
T₀
T₀
。T
- 隐式转换涉及已知为引用类型的类型参数。 有关涉及类型参数的隐式转换的更多详细信息,请参阅 §10.2.12 。
隐式引用转换是reference_type之间的这些转换,可以证明始终成功,因此无需在运行时进行检查。
引用转换(隐式或显式)永远不会更改所转换对象的引用标识。
注意:换句话说,虽然引用转换可以更改引用的类型,但它永远不会更改所引用对象的类型或值。 end note
10.2.9 装箱转换
装箱转换允许隐式转换为reference_type value_type。 存在以下装箱转换:
- 从任何 value_type 到类型
object
。 - 从任何 value_type 到类型
System.ValueType
。 - 从任何 enum_type 到类型
System.Enum
。 - 从任何non_nullable_value_type到由non_nullable_value_type实现的任何interface_type。
- 从任何non_nullable_value_type到任何interface_type
I
,以便从non_nullable_value_type转换为另一个interface_typeI₀
,并I₀
具有标识转换。I
- 从任何non_nullable_value_type到任何interface_type
I
,使拳击从non_nullable_value_type到另一个interface_typeI₀
,并且I₀
是方差可转换(§18.2.3.3)到I
。 - 从任何nullable_value_type到从nullable_value_type的基础类型转换为reference_type的任何reference_type。
- 从未知为引用类型的类型参数到任何类型的类型,以便 §10.2.12 允许转换。
将不可为 null 值类型的值装箱包括分配对象实例并将该值复制到该实例。
如果值为 null 值(HasValue
为 false),或者取消包装并装箱基础值的结果,则装箱nullable_value_type将生成 null 引用。
注意:根据每种值类型的装箱类的存在,可以想象装箱过程。 例如,考虑
struct S
实现接口I
,并调用了一个装S_Boxing
箱类。interface I { void M(); } struct S : I { public void M() { ... } } sealed class S_Boxing : I { S value; public S_Boxing(S value) { this.value = value; } public void M() { value.M(); } }
现在,装箱一个类型的
S
值v
包括执行表达式new S_Boxing(v)
,并将生成的实例作为转换的目标类型的值返回。 因此,语句S s = new S(); object box = s;
可被视为类似于:
S s = new S(); object box = new S_Boxing(s);
上述想象中的装箱类型实际上不存在。 相反,类型的
S
装箱值具有运行时类型S
,并使用具有值类型的运算符进行运行时类型检查is
,因为右操作数测试左操作数是否为右操作数的装箱版本。 例如,int i = 123; object box = i; if (box is int) { Console.Write("Box contains an int"); }
将输出以下内容:
Box contains an int
装箱转换意味着创建正在装箱的值的副本。 这不同于reference_type到类型的
object
转换,其中值继续引用同一实例,只是被视为派生类型object
较少。 例如,以下各项struct Point { public int x, y; public Point(int x, int y) { this.x = x; this.y = y; } } class A { void M() { Point p = new Point(10, 10); object box = p; p.x = 20; Console.Write(((Point)box).x); } }
将输出主机上的值 10,因为分配
p
box
中发生的隐式装箱操作会导致复制值p
。 如果Point
已改为声明class
值 20,则输出值 20,因为p
会box
引用同一实例。拳击类的类比不应用作一个有用的工具,用于说明拳击在概念上的工作原理。 此规范描述的行为与以这种方式精确实现装箱的行为之间存在许多细微差异。
end note
10.2.10 隐式动态转换
隐式动态转换从动态类型表达式到任何类型 T
。 转换是动态绑定 §12.3.3,这意味着将在运行时从表达式的运行时类型到 T
该表达式的隐式转换。 如果未找到任何转换,则会引发运行时异常。
这种隐式转换似乎违反了 §10.2 开头的建议,即隐式转换绝不会导致异常。 但是,它不是转换本身,而是 导致异常的转换结果 。 运行时异常的风险固有于使用动态绑定。 如果不需要转换的动态绑定,则可以先将表达式转换为所需类型,然后再转换为 object
所需类型。
示例:下面说明了隐式动态转换:
object o = "object"; dynamic d = "dynamic"; string s1 = o; // Fails at compile-time – no conversion exists string s2 = d; // Compiles and succeeds at run-time int i = d; // Compiles but fails at run-time – no conversion exists
对操作的绑定
s2
和i
都采用隐式动态转换,其中操作的绑定在运行时挂起。 在运行时,从 (string
) 的d
运行时类型到目标类型寻求隐式转换。 找到转换到string
但不转换为int
。end 示例
10.2.11 隐式常量表达式转换
隐式常量表达式转换允许以下转换:
- 类型constant_expression(§12.23)
int
可以转换为类型sbyte
、byte
、、ushort
short
、或uint
ulong
constant_expression的值在目标类型范围内。 - 类型constant_expression可以转换为类型
ulong
long
,前提是constant_expression的值不是负值。
10.2.12 涉及类型参数的隐式转换
对于已知为引用类型的type_parameterT
(§15.2.5),存在以下隐式引用转换(§10.2.8):
- 从
T
其有效的基类C
,从T
任何基类,从任何基类C
,从任何T
接口实现C
。 - 从
T
interface_typeT
I
的有效接口集和从T
任何基接口。I
- 从
T
类型参数U
提供,T
具体取决于U
(§15.2.5)。注意:由于
T
已知是引用类型,因此在运行时类型的范围内T
,运行时类型U
将始终为引用类型,即使U
在编译时不知道是引用类型。 end note - 从 null 文本(§6.4.5.7)到 T。
对于未知为引用类型 §15.2.5 的 T
type_parameter,在编译时,涉及T
的以下转换被视为装箱转换(§10.2.9)。 在运行时,如果 T
为值类型,则转换将作为装箱转换执行。 在运行时,如果是 T
引用类型,则转换将作为隐式引用转换或标识转换执行。
- 从
T
其有效的基类C
,从T
任何基类,从任何基类C
,从任何T
接口实现C
。注意:
C
将是其中一种类型System.Object
,System.ValueType
或者System.Enum
(否则T
已知为引用类型)。 end note - 从
T
interface_typeT
I
的有效接口集和从T
任何基接口。I
对于未知为引用类型的type_parameter T
,提供的T
类型参数U
有隐式转换T
取决于U
。 在运行时,如果是 T
值类型并且 U
是引用类型,则转换将作为装箱转换执行。 在运行时,如果两者都是T
U
值类型,则T
并且U
一定是相同的类型,并且不执行任何转换。 在运行时,如果是 T
引用类型,则 U
一定也是引用类型,并且转换作为隐式引用转换或标识转换(§15.2.5)执行。
给定类型参数 T
存在以下进一步隐式转换:
- 如果引用类型具有对引用类型的隐式转换,并且具有标识转换到的引用类型,则从
T
引用类型S
到S
引用类型S₀
。S₀
在运行时,转换的执行方式与转换方式S₀
相同。 - 如果
T
接口类型具有对接口类型的I
I₀
隐式转换,并且I₀
可转换为方差(I
§18.2.3.3.3)。 在运行时,如果T
为值类型,则转换将作为装箱转换执行。 否则,转换将作为隐式引用转换或标识转换执行。
在所有情况下,规则都确保在运行时将转换作为装箱转换执行,前提是在运行时转换是从值类型转换为引用类型。
10.2.13 隐式元组转换
如果E
元组表达式与元组类型T
相同T
,并且每个元素中E
存在隐式转换,则存在从元组表达式E
到元组类型的T
隐式转换。 转换是通过创建相应类型的实例T
来执行的,并通过计算相应的元组元素表达式E
、使用找到的隐式转换将其转换为相应的元素类型T
以及使用结果初始化字段的顺序从左到右初始化其每个System.ValueTuple<...>
字段。
如果元组表达式中的元素名称与元组类型的相应元素名称不匹配,应发出警告。
示例:
(int, string) t1 = (1, "One"); (byte, string) t2 = (2, null); (int, string) t3 = (null, null); // Error: No conversion (int i, string s) t4 = (i: 4, "Four"); (int i, string) t5 = (x: 5, s: "Five"); // Warning: Names are ignored
t1
t4
t2
声明和t5
所有有效,因为从元素表达式到相应的元素类型存在隐式转换。 声明t3
无效,因为没有从中null
转换到int
。t5
声明会导致警告,因为元组表达式中的元素名称不同于元组类型中的元素名称。end 示例
10.2.14 用户定义的隐式转换
用户定义的隐式转换由可选的标准隐式转换组成,后跟用户定义隐式转换运算符的执行,后跟另一个可选的标准隐式转换。 用于评估用户定义的隐式转换的确切规则在 §10.5.4 中介绍。
10.2.15 匿名函数转换和方法组转换
匿名函数和方法组本身没有类型,但它们可能隐式转换为委托类型。 此外,某些 lambda 表达式可以隐式转换为表达式树类型。 §10.7 和 §10.8 中的方法组转换更详细地描述了匿名函数转换。
10.2.16 默认文本转换
隐式转换从 default_literal (§12.8.21)转换为任何类型。 此转换生成推断类型的默认值(§9.3)。
10.2.17 隐式引发转换
虽然引发表达式没有类型,但它们可能隐式转换为任何类型。
10.3 显式转换
10.3.1 常规
以下转换被归类为显式转换:
- 所有隐式转换 (§10.2)
- 显式数值转换 (§10.3.2)
- 显式枚举转换 (§10.3.3)
- 显式可为 null 的转换(§10.3.4)
- 显式元组转换 (§10.3.6)
- 显式引用转换 (§10.3.5)
- 显式接口转换
- 取消装箱转换(§10.3.7)
- 显式类型参数转换 (§10.3.8)
- 用户定义的显式转换(§10.3.9)
显式转换可以在强制转换表达式(§12.9.7)中发生。
显式转换集包括所有隐式转换。
注意:例如,允许在存在隐式标识转换时使用显式强制转换,以强制选择特定方法重载。 end note
非隐式转换的显式转换是无法证明始终成功的转换、已知可能丢失信息的转换,以及跨类型域的转换,与值得显式表示法完全不同的转换。
10.3.2 显式数值转换
显式数值转换是从numeric_type转换为另一个numeric_type,其中隐式数值转换(§10.2.3)尚不存在:
- 从
sbyte
到byte
、ushort
、uint
、ulong
或char
。 - 从
byte
/或char
sbyte
. - 从
short
到、byte
、ushort
、uint
、ulong
或char
。sbyte
- From
ushort
tosbyte
、byte
,short
or.char
- 从
int
、byte
、short
、ushort
、uint
、或ulong
char
。sbyte
- 从
uint
到、byte
、short
、ushort
、int
或char
。sbyte
- 从
long
、sbyte
byte
、short
、ushort
、int
、uint
、或ulong
char
。 - 从
ulong
、sbyte
byte
、short
、ushort
、int
、uint
、或long
char
。 - 从
char
到sbyte
,byte
或short
。 - 从
float
、byte
、short
、ushort
、、int
uint
、long
、ulong
、或char
decimal
。sbyte
- 从
double
、byte
、、short
ushort
、、int
、uint
ulong
long
、、char
、或float
decimal
。sbyte
- 从
decimal
、byte
、、short
ushort
、、int
、uint
ulong
long
、、char
、或float
double
。sbyte
由于显式转换包括所有隐式和显式数值转换,因此始终可以使用强制转换表达式(§12.9.7)从任何numeric_type转换为任何其他numeric_type。
显式数值转换可能会丢失信息,或者可能导致引发异常。 显式数值转换按如下方式进行处理:
- 对于从整型类型到另一个整型类型的转换,处理取决于发生转换的溢出检查上下文(§12.8.20):
checked
在上下文中,如果源操作数的值在目标类型范围内,则转换成功,但如果源操作数的值超出目标类型的范围,则会引发该System.OverflowException
转换。unchecked
在上下文中,转换始终成功,并继续如下。- 如果源类型大于目标类型,则通过放弃其“额外”最重要的位来截断源值。 结果会被视为目标类型的值。
- 如果源类型的大小与目标类型相同,则源值被视为目标类型的值
- 对于从
decimal
整型到整型的转换,源值将舍入为零到最接近的整型值,此整型值将成为转换的结果。 如果生成的整型值超出目标类型的范围,则会引发 aSystem.OverflowException
。 - 对于从
float
或double
转换为整型类型,处理取决于发生转换的溢出检查上下文(§12.8.20):- 在选中的上下文中,转换将按如下所示进行:
- 如果操作数的值为 NaN 或无限,则会引发 a
System.OverflowException
。 - 否则,源操作数将舍入为零到最接近的整数值。 如果此整型值在目标类型的范围内,则此值是转换的结果。
- 否则,将会引发
System.OverflowException
。
- 如果操作数的值为 NaN 或无限,则会引发 a
- 在未选中的上下文中,转换始终成功,并继续执行如下操作。
- 如果操作数的值为 NaN 或无限,则转换的结果是目标类型的未指定值。
- 否则,源操作数将舍入为零到最接近的整数值。 如果此整型值在目标类型的范围内,则此值是转换的结果。
- 否则,转换的结果是目标类型的未指定值。
- 在选中的上下文中,转换将按如下所示进行:
- 对于从中
double
转换到float
的值,该值double
将舍入为最接近float
的值。double
如果值太小而无法表示为 afloat
,则结果将变为零,其符号与值相同。 如果值的大小double
太大而无法表示为 afloat
,则结果将变为无穷大,其符号与值相同。double
如果值为 NaN,则结果也是 NaN。 - 对于从
float
或转换到decimal
的转换,源值将转换为decimal
表示形式,并在需要时舍入到最接近的数字(§8.3.8double
)。- 如果源值太小而无法表示为 a
decimal
,则结果为零,如果decimal
支持有符号零值,则保留原始值的符号。 - 如果源值的大小太大而无法表示为
decimal
无穷大,或者该值为无穷大,则结果为无限保留原始值的符号(如果小数表示形式支持无数);否则将引发 System.OverflowException。 - 如果源值为 NaN,则如果十进制表示形式支持 NaN,则结果为 NaN;否则将引发 System.OverflowException。
- 如果源值太小而无法表示为 a
- 对于从或转换
decimal
,float
该值decimal
舍入为最接近double
的值或float
值。double
如果源值的大小太大,无法在目标类型中表示,或者该值为无穷大,则结果将无限保留原始值的符号。 如果源值为 NaN,则结果为 NaN。 虽然此转换可能会丢失精度,但它永远不会引发异常。
注意:类型
decimal
不是支持无限值或 NaN 值所必需的,但可能这样做;其范围可能小于float
范围,double
但不能保证是。 对于decimal
没有无限值或 NaN 值的表示形式,并且范围小于float
,则从decimal
任一转换到任float
double
一值或永远不会是无穷大或 NaN 的结果。 end note
10.3.3 显式枚举转换
显式枚举转换为:
- 从
sbyte
、byte
、short
、ushort
、int
、、uint
、ulong
long
char
float
、或decimal
double
任意enum_type。 - 从任何enum_type到
sbyte
、、byte
、ushort
short
、int
uint
、、long
、ulong
char
、float
、或double
decimal
。 - 从任何 enum_type 到任何其他 enum_type。
将任何参与enum_type视为该enum_type的基础类型,然后在结果类型之间执行隐式或显式数值转换,来处理两种类型之间的显式枚举转换。
示例:给定一个具有和基础类型的
int
enum_typeE
,从byte
E
到的转换将作为显式数值转换(§10.3.2)进行处理byte
int
,从其进行转换byte
E
作为隐式数值转换(§10.2.3)进行处理。byte
int
end 示例
10.3.4 显式可为 null 的转换
显式可为 null 转换是从显式和隐式预定义转换派生的那些可为 null 的转换(§10.6.1)。
10.3.5 显式引用转换
显式引用转换包括:
- 从对象到任何其他 reference_type。
- 从任何class_type
S
到任何class_typeT
,提供的S
是基类T
。 - 从任何class_type
S
到任何interface_typeT
,未S
密封,并且未S
实现T
。 - 从任何interface_type
S
到任何class_typeT
,未T
密封或提供T
实现S
。 - 从任何interface_type
S
到任何interface_typeT
,提供的S
不是派生自T
。 - 从具有元素类型的array_type
S
到具有元素类型的Sᵢ
Tᵢ
array_typeT
,前提是以下所有内容均为 true:S
并且T
仅在元素类型中不同。 换句话说,S
维度T
数相同。- 显式引用转换从 <
a0/> 到 Tᵢ
.
- 从
System.Array
它实现的接口到任何 array_type。 - 从单维array_type
S[]
到System.Collections.Generic.IList<T>
,System.Collections.Generic.IReadOnlyList<T>
以及其基接口,前提是存在标识转换或从中显式引用转换S
。T
- 从
System.Collections.Generic.IList<S>
System.Collections.Generic.IReadOnlyList<S>
到单维数组类型的T[]
基接口,前提是存在从 T 到 T 的标识转换或显式引用转换S
。 - 它
System.Delegate
实现到任何 delegate_type的接口。 - 如果引用类型具有从引用类型
S
到引用类型的显式引用转换,并且T₀
从引用类型进行标识转换,则从引用T₀
类型T
转换为T
引用类型T₀
。S
- 从引用类型到接口或委托类型(如果存在从
S
接口或委托类型进行显式引用转换且可转换为方差转换为T
T
§18.2.3.3)。从引用类型S
到T₀
接口或委托类型T
T₀
T₀
。 - 从
D<S₁...Sᵥ>
何处D<X₁...Xᵥ>
是泛型委托类型,D<S₁...Sᵥ>
与以下类型的每种类型参数D
Xᵢ
不兼容或相同D<T₁...Tᵥ>
:D<T₁...Tᵥ>
- 如果
Xᵢ
为固定值,则Sᵢ
与Tᵢ
.. - 如果
Xᵢ
为协变,则存在标识转换、隐式引用转换或从Sᵢ
中显式引用转换。Tᵢ
- 如果
Xᵢ
为逆变量,则Sᵢ
为Tᵢ
相同或两种引用类型。
- 如果
- 涉及已知为引用类型的类型参数的显式转换。 有关涉及类型参数的显式转换的更多详细信息,请参阅 §10.3.8。
显式引用转换是reference_type之间的转换,这些转换需要运行时检查以确保它们正确。
要使显式引用转换在运行时成功,源操作数的值应为 null
,或者源操作数引用的对象的类型应为可通过隐式引用转换(§10.2.8)转换为目标类型的类型。 如果显式引用转换失败,则会引发 a System.InvalidCastException
。
注意:引用转换、隐式或显式转换永远不会更改引用本身的值(§8.2.1),仅更改其类型;两者都不会更改所引用对象的类型或值。 end note
10.3.6 显式元组转换
如果E
元组表达式与元组类型T
相同,并且每个元素中的隐式或显式转换与 中的T
E
相应元素类型相同T
,则存在从元组表达式E
到元组类型的显式转换。 转换是通过创建相应类型的实例T
,并通过计算相应的元组元素表达式E
,将每个字段初始化为从左到右的顺序初始化其每个字段,并将其转换为使用找到的显式转换的相应元素类型T
,以及用结果初始化System.ValueTuple<...>
字段。
10.3.7 取消装箱转换
取消装箱转换允许显式转换为value_type reference_type。 存在以下取消装箱转换:
- 从类型
object
到任何 value_type。 - 从类型
System.ValueType
到任何 value_type。 - 从类型
System.Enum
到任何 enum_type。 - 从任何interface_type到实现interface_type的任何non_nullable_value_type。
- 从任何interface_type
I
到任何non_nullable_value_type,其中从interface_type到non_nullable_valueI₀
类型的取消装箱转换,以及从I
中转换到I₀
的标识转换。 - 从任何interface_type
I
到任何non_nullable_value_type,其中从interface_typeI₀
到non_nullable_value_typeI₀
的取消装箱转换,要么variance_convertibleI
I
或可转换为方差(I₀
§18.2.3.3)。 - 从任何reference_type到从reference_type到nullable_value_type的基础non_nullable_value_type进行取消装箱转换的任何nullable_value_type。
- 从未知为值类型的类型参数到任何类型的类型,以便 §10.3.8 允许转换。
对non_nullable_value_type的取消装箱操作包括首先检查对象实例是否为给定non_nullable_value_type的装箱值,然后将该值从实例复制出来。
如果源操作数为null
空,则取消装箱到nullable_value_type会生成nullable_value_type的 null 值,或者将对象实例取消装箱到nullable_value_type的基础类型的包装结果。
注意:引用 §10.2.9 中描述的虚构装箱类,将对象框取消装箱转换为value_type
S
由执行表达式((S_Boxing)box).value
组成。 因此,语句object box = new S(); S s = (S)box;
概念上对应于
object box = new S_Boxing(new S()); S s = ((S_Boxing)box).value;
end note
若要在运行时成功对给定 non_nullable_value_type 的取消装箱转换,源操作数的值应为对该 non_nullable_value_type的装箱值引用。 如果引发源操作数null
System.NullReferenceException
。 如果源操作数是对不兼容对象的引用,则会引发一个 System.InvalidCastException
操作数。
若要在运行时成功对给定nullable_value_type的取消装箱转换,源操作数的值应为 null 或对nullable_value_type的基础non_nullable_value_type的装箱值引用。 如果源操作数是对不兼容对象的引用,则会引发一个 System.InvalidCastException
操作数。
10.3.8 涉及类型参数的显式转换
对于已知为引用类型的type_parameterT
(§15.2.5),存在以下显式引用转换(§10.3.5):
- 从有效基类
C
T
到T
/从任何基类C
到T
. - 从任何 interface_type 到
T
。 - 从
T
到提供的任何interface_typeI
,还没有从隐式引用转换T
到I
。 - 从type_parameter
U
提供U
T
T
取决于 (§15.2.5) 。注意:由于
T
已知是引用类型,因此在运行时类型中T
,运行时类型将始终是引用类型,即使U
不知道在编译时是引用类型。 end note
对于未知为引用类型的type_parameterT
(§15.2.5),在编译时,涉及的以下转换T
被视为取消装箱转换(§10.3.7)。 在运行时,如果 T
为值类型,则转换将作为取消装箱转换执行。 在运行时,如果是 T
引用类型,则转换将作为显式引用转换或标识转换执行。
- 从有效基类
C
T
到T
/从任何基类C
到T
.注意:C 将是其中一种类型
System.Object
,System.ValueType
或者System.Enum
(否则T
已知为引用类型)。 end note - 从任何 interface_type 到
T
。
对于未知为引用类型的type_parameter T
(§15.2.5),存在以下显式转换:
- 从
T
到任何interface_typeI
提供,还没有从隐式转换T
到I
。 此转换由隐式装箱转换(§10.2.9)组成object
T
,后跟从显式引用转换object
到I
。 在运行时,如果T
为值类型,则转换将作为装箱转换执行,后跟显式引用转换。 在运行时,如果是T
引用类型,则转换将作为显式引用转换执行。 - 从依赖于的类型参数
U
(§15.2.5)提供T
U
。T
在运行时,如果是T
值类型并且U
是引用类型,则转换将作为取消装箱转换执行。 在运行时,如果两者都是T
U
值类型,则T
并且U
一定是相同的类型,并且不执行任何转换。 在运行时,如果是T
引用类型,则U
一定也是引用类型,并且转换作为显式引用转换或标识转换执行。
在所有情况下,规则确保转换作为取消装箱转换执行,前提是在运行时转换是从引用类型转换为值类型。
上述规则不允许从不受约束的类型参数直接转换为非接口类型,这可能令人吃惊。 此规则的原因是防止混淆,并明确此类转换的语义。
示例:请考虑以下声明:
class X<T> { public static long F(T t) { return (long)t; // Error } }
如果允许直接显式转换
t
到long
该转换,人们很容易期望X<int>.F(7)
会返回7L
。 但是,它不会,因为只有在确定类型在绑定时为数值时才考虑标准数值转换。 为了明确语义,必须改为编写上述示例:class X<T> { public static long F(T t) { return (long)(object)t; // Ok, but will only work when T is long } }
此代码现在将编译,但执行
X<int>.F(7)
将在运行时引发异常,因为装箱int
无法直接转换为 along
。end 示例
10.3.9 用户定义的显式转换
用户定义的显式转换由可选的标准显式转换组成,然后执行用户定义的隐式或显式转换运算符,后跟另一个可选的标准显式转换。 用于评估用户定义的显式转换的确切规则在 §10.5.5 中介绍。
10.4 标准转换
10.4.1 常规
标准转换是可以作为用户定义的转换的一部分发生的预定义转换。
10.4.2 标准隐式转换
以下隐式转换被归类为标准隐式转换:
- 标识转换 (§10.2.2)
- 隐式数值转换 (§10.2.3)
- 隐式可为 null 转换 (§10.2.6)
- Null 文本转换 (§10.2.7)
- 隐式引用转换 (§10.2.8)
- 装箱转换 (§10.2.9)
- 隐式常量表达式转换 (§10.2.11)
- 涉及类型参数的隐式转换 (§10.2.12)
标准隐式转换专门排除用户定义的隐式转换。
10.4.3 标准显式转换
标准显式转换都是标准隐式转换,以及存在相反标准隐式转换的显式转换的子集。
注意:换句话说,如果从类型到类型
A
B
存在标准隐式转换,则从类型到类型以及从类型A
B
到类型B
A
存在标准显式转换。 end note
10.5 用户定义的转换
10.5.1 常规
C# 允许用户定义的转换扩充预定义的隐式和显式转换。 用户定义的转换是通过在类和结构类型中声明转换运算符 (§15.10.4)来引入的。
10.5.2 允许的用户定义转换
C# 仅允许声明某些用户定义的转换。 具体而言,无法重新定义现有的隐式或显式转换。
对于给定的源类型和S
目标类型,如果S
或T
为可为 null 的值类型T
,请让S₀
和T₀
引用其基础类型,否则S₀
,它们T₀
分别等于S
和T
分别。 仅当以下所有内容均为 true 时,才允许类或结构声明从源类型 S
转换为目标类型 T
:
S₀
和T₀
不同类型的。S₀
T₀
运算符声明所在的类或结构类型。- 既
S₀
T₀
不是interface_type。 - 排除用户定义的转换,转换不存在于
S
或从T
中T
转换S
。
适用于用户定义的转换的限制在 §15.10.4 中指定。
10.5.3 用户定义的转换评估
用户定义的转换将源表达式(可能具有源类型)转换为另一种类型,称为目标类型。 用户定义转换的评估侧重于查找 源表达式和目标类型的最具体的 用户定义的转换运算符。 此决定分为几个步骤:
- 查找将从中考虑用户定义的转换运算符的类和结构集。 如果源类型存在,则此集由源类型及其基类以及目标类型和基类组成。 为此,假定只有类和结构可以声明用户定义的运算符,并且非类类型没有基类。 此外,如果源类型或目标类型为 nullable-value-type,则改用其基础类型。
- 从该类型集中,确定哪些用户定义的和提升转换运算符适用。 要使转换运算符适用,可以从源表达式执行从源表达式到运算符操作数类型的标准转换(§10.4),并且可以执行从运算符的结果类型到目标类型的标准转换。
- 从一组适用的用户定义的运算符中,确定哪个运算符最明确。 一般情况下,最具体的运算符是操作数类型与源表达式“最接近”的运算符,其结果类型与目标类型“最接近”。 用户定义的转换运算符优先于提升的转换运算符。 用于建立最具体的用户定义的转换运算符的确切规则在以下子项中定义。
确定最具体的用户定义转换运算符后,用户定义转换的实际执行最多涉及三个步骤:
- 首先,如果需要,执行从源表达式到用户定义或提升转换运算符的操作数类型的标准转换。
- 接下来,调用用户定义的或提升转换运算符来执行转换。
- 最后,如果需要,请执行从用户定义的转换运算符的结果类型到目标类型的标准转换。
用户定义转换的计算绝不涉及多个用户定义的或提升转换运算符。 换句话说,从类型S
到类型的T
转换永远不会首先执行用户定义的转换S
,X
然后执行用户定义的转换。X
T
- 以下子集提供了用户定义隐式转换或显式转换评估的确切定义。 这些定义使用以下术语:
- 如果标准隐式转换(§10.4.2)从类型
A
到类型B
存在,如果两A
者都不B
interface_types
,A
则表示B
包含,据说B
包含。A
- 如果标准隐式转换(§10.4.2)从表达式
E
到一种类型B
存在,并且(如果B
它有一个)的类型都没有E
interface_types
,则E
据说由它包含B
,并且B
据说包含E
。 - 一 组类型中最包含的类型 是包含集中所有其他类型的一种类型。 如果没有单一类型包含所有其他类型,则集没有最包含的类型。 在更直观的术语中,最包含的类型是集中的“最大”类型,即可以隐式转换其他每种类型的类型。
- 一 组类型中最包含的类型 是集中所有其他类型所包含的一种类型。 如果所有其他类型均未包含任何单个类型,则集没有最包含的类型。 在更直观的术语中,最包含的类型是集中的“最小”类型,即可以隐式转换为其他每种类型的类型。
10.5.4 用户定义的隐式转换
将用户定义的隐式转换从表达式 E
转换为类型 T
,如下所示:
确定类型
S
,S₀
以及T₀
。- 如果有
E
类型,请将其S
指定为该类型。 - 如果
S
或T
为可以为 null 的值类型,则让Sᵢ
和Tᵢ
其基础类型,否则分别让Sᵢ
和Tᵢ
和S
T
。 - 如果
Sᵢ
或Tᵢ
为类型参数,则让S₀
和T₀
成为其有效的基类,否则分别让S₀
和T₀
成为Sₓ
它们Tᵢ
。
- 如果有
查找一组类型,
D
从中将考虑用户定义的转换运算符。 此集由(如果S₀
存在且是类或结构)、基类S₀
(如果S₀
存在且为类)和T₀
(如果T₀
为类或结构)组成S₀
。 仅当标识转换到该集中已包含的另一个类型不存在时,才会将类型添加到D
集中。查找一组适用的用户定义的和提升的转换运算符。
U
此集由用户定义和提升的隐式转换运算符组成,这些运算符由类或结构D
声明,这些运算符从包含的类型转换为所T
包含E
的类型。 如果U
为空,则转换未定义,并且会发生编译时错误。- 如果
S
存在并且转换后S
的任何运算符U
,则Sₓ
为S
。 - 否则,
Sₓ
是运算符的一组组合类型中最包含的类型U
。 如果找不到完全包含的类型,则转换不明确,并且会发生编译时错误。
- 如果
在以下项中找到
U
最具体的目标类型Tₓ
:- 如果转换到
T
的任何运算符U
,则Tₓ
为T
。 - 否则,
Tₓ
是运算符的一组组合目标类型中最包含的类型U
。 如果找不到完全包含的一种类型,则转换不明确,并且会发生编译时错误。
- 如果转换到
查找最具体的转换运算符:
- 如果
U
正好包含一个从中转换Sₓ
到Tₓ
的用户定义转换运算符,则这是最具体的转换运算符。 - 否则,如果
U
正好包含一个从Sₓ
中转换到Tₓ
的提升转换运算符,则这是最具体的转换运算符。 - 否则,转换不明确且发生编译时错误。
- 如果
最后,应用转换:
- 如果 E 还没有该类型,则执行从
E
该类型Sₓ
到Sₓ
的标准隐式转换。 - 调用最具体的转换运算符以从
Sₓ
中Tₓ
转换为 。 - 如果
Tₓ
不是T
,则执行从标准隐式转换Tₓ
到T
该转换。
- 如果 E 还没有该类型,则执行从
如果用户定义的隐式转换从类型S
到类型T
存在,则存在从类型变量到T
该类型的S
隐式转换。
10.5.5 用户定义的显式转换
将用户定义的显式转换从表达式 E
转换为类型 T
,如下所示:
- 确定类型
S
,S₀
以及T₀
。- 如果有
E
类型,请将其S
指定为该类型。 - 如果
S
或T
为可以为 null 的值类型,则让Sᵢ
和Tᵢ
其基础类型,否则分别让Sᵢ
和Tᵢ
和S
T
。 - 如果
Sᵢ
或Tᵢ
为类型参数,则让S₀
和T₀
成为其有效的基类,否则分别让S₀
和T₀
成为Sᵢ
它们Tᵢ
。
- 如果有
- 查找一组类型,
D
从中将考虑用户定义的转换运算符。 此集由(如果S₀
存在且是类或结构)、(如果S₀
存在且为类)、T₀
(如果T₀
为类或结构)的基类S₀
以及(如果T₀
为类)的基类T₀
组成S₀
。A
仅当标识转换为该集中已包含的另一个类型不存在时,类型才会添加到D
集中。 - 查找一组适用的用户定义的和提升的转换运算符。
U
此集由用户定义和提升的隐式或显式转换运算符组成,这些运算符由类或结构D
声明,这些运算符从包含或包含S
的类型(如果存在)转换为包含E
或包含的类型T
。 如果U
为空,则转换未定义,并且会发生编译时错误。 - 在下列运算符中找到
U
最具体的源类型Sₓ
:- 如果 S 存在并且转换后
S
的任何运算符U
,则Sₓ
为S
。 - 否则,如果任何从包含
E
的类型转换的运算符U
,则Sₓ
这些运算符的组合源类型的集合中是最包含的类型。 如果找不到最包含的类型,则转换不明确,并且会发生编译时错误。 - 否则,
Sₓ
是运算符的一组组合的源类型中最包含的类型U
。 如果找不到完全包含的一种类型,则转换不明确,并且会发生编译时错误。
- 如果 S 存在并且转换后
- 在以下项中找到
U
最具体的目标类型Tₓ
:- 如果转换到
T
的任何运算符U
,则Tₓ
为T
。 - 否则,如果任何运算符
U
转换为所T
包含的类型,则Tₓ
这些运算符的组合目标类型集中最包含的类型。 如果找不到完全包含的一种类型,则转换不明确,并且会发生编译时错误。 - 否则,
Tₓ
是运算符的一组组合目标类型中最包含的类型U
。 如果找不到最包含的类型,则转换不明确,并且会发生编译时错误。
- 如果转换到
- 查找最具体的转换运算符:
- 如果 U 仅包含一个从
Sₓ
中转换到Tₓ
的用户定义转换运算符,则这是最具体的转换运算符。 - 否则,如果
U
正好包含一个从Sₓ
中转换到Tₓ
的提升转换运算符,则这是最具体的转换运算符。 - 否则,转换不明确且发生编译时错误。
- 如果 U 仅包含一个从
- 最后,应用转换:
- 如果
E
还没有该类型Sₓ
,则执行从 E 到Sₓ
的标准显式转换。 - 调用最具体的用户定义的转换运算符以从
Sₓ
中转换到Tₓ
。 T
如果没有Tₓ
,则执行从标准显式转换Tₓ
到T
该转换。
- 如果
如果用户定义的显式转换从类型到类型S
T
存在,则存在从类型到T
的S
变量的用户定义的显式转换。
涉及可为 null 类型的 10.6 转换
10.6.1 可为 Null 的转换
可以为 null 的转换允许对不可为 null 的值类型进行操作的预定义转换 ,这些类型也可用于这些类型的可为 null 形式。 对于从不可为 null 值类型转换为不可为 null 值类型的S
T
每个预定义隐式或显式转换(§10.2.2、§10.2.3、§10.2.4、§10.2.11、§10.3.2 和 §10.3.3),存在以下可为 null 转换:
- 从隐式或显式转换
S?
到T?
- 从隐式或显式转换
S
到T?
- 从显式转换
S?
到T
.
可为 null 的转换本身被归类为隐式或显式转换。
某些可为 null 的转换被归类为标准转换,并可以作为用户定义的转换的一部分进行。 具体而言,所有隐式可为 null 转换都归类为标准隐式转换(§10.4.2),满足 §10.4.3 要求的显式可为 null 转换被归类为标准显式转换。
基于基础转换从基础转换到T
收益计算可为 null 的转换S
,如下所示:
- 如果可为 null 的转换从以下值
S?
转换为T?
:- 如果源值为 null(
HasValue
属性为false
),则结果为类型T?
为 null 值。 - 否则,将转换计算为从中解包
S?
,后跟基础转换S
,T
然后从中换行T?
T
。S
- 如果源值为 null(
- 如果可为 null 的转换是从
S
到T?
的,则将转换计算为基础转换,S
T
后跟从换T
行。T?
- 如果可为 null 的转换是从
S?
到T
的,则将转换计算为从中解包S?
,S
然后从基础转换到T
基础转换S
。
10.6.2 提升转换
给定用户定义的转换运算符,该运算符从不可为 null 的值类型转换为不可为 null 的值类型S
T
,则存在从S?
中转换到T?
的提升转换运算符。 此提升转换运算符执行从用户定义转换到S
后跟换行T?
T
的解包S?
,但 null 值S?
直接转换为 null 值T?
。T
S
提升的转换运算符与其基础用户定义的转换运算符具有相同的隐式或显式分类。
10.7 匿名函数转换
10.7.1 常规
anonymous_method_expression或lambda_expression被归类为匿名函数(§12.19)。 表达式没有类型,但可以隐式转换为兼容的委托类型。 某些 lambda 表达式也可能隐式转换为兼容的表达式树类型。
具体而言,匿名函数 F
与提供的委托类型 D
兼容:
- 如果
F
包含 anonymous_function_signature,则D
具有相同F
数量的参数。 - 如果
F
不包含 anonymous_function_signature,则D
只要没有参数是输出参数,就可能具有任何类型的零个或多个参数D
。 - 如果
F
具有显式类型化参数列表,则每个参数的D
修饰符与相应的参数相同,并且存在相应参数F
F
之间的标识转换。 - 如果
F
具有隐式类型化参数列表,D
则没有引用或输出参数。 - 如果正文
F
是表达式,并且D
具有 void 返回类型或F
异步且D
具有«TaskType»
返回类型(§15.15.1),则当给定相应参数D
类型的每个参数F
时,正文是一个有效的表达式(w.r.t §12),该表达式的正文F
将被允许为statement_expression(§13.7)。 - 如果正文
F
是块,并且D
具有 void 返回类型或F
是异步的,并且D
具有«TaskType»
返回类型,则当给定相应参数F
D
的类型时,F
正文是有效块(w.r.t §13.3),其中没有return
语句指定表达式。 - 如果正文
F
是表达式,要么F
是非异步类型,要么D
是void
非返回类型T
,要么F
是异步的,并且D
具有«TaskType»<T>
返回类型(§15.15.1),则当给定相应参数D
F
的类型时,正文F
是可隐式转换为T
的有效表达式(w.r.t §12)。 - 如果主体
F
是块,要么F
是非异步的,并且D
具有非空返回类型T
,或者是F
异步的,并且D
具有«TaskType»<T>
返回类型,则当每个参数的类型都给定相应参数F
D
的类型时,正文F
是一个有效的语句块(w.r.t §13.3),其中每个返回语句指定可隐式转换为T
的表达式。
示例:以下示例演示了以下规则:
delegate void D(int x); D d1 = delegate { }; // Ok D d2 = delegate() { }; // Error, signature mismatch D d3 = delegate(long x) { }; // Error, signature mismatch D d4 = delegate(int x) { }; // Ok D d5 = delegate(int x) { return; }; // Ok D d6 = delegate(int x) { return x; }; // Error, return type mismatch delegate void E(out int x); E e1 = delegate { }; // Error, E has an output parameter E e2 = delegate(out int x) { x = 1; }; // Ok E e3 = delegate(ref int x) { x = 1; }; // Error, signature mismatch delegate int P(params int[] a); P p1 = delegate { }; // Error, end of block reachable P p2 = delegate { return; }; // Error, return type mismatch P p3 = delegate { return 1; }; // Ok P p4 = delegate { return "Hello"; }; // Error, return type mismatch P p5 = delegate(int[] a) // Ok { return a[0]; }; P p6 = delegate(params int[] a) // Error, params modifier { return a[0]; }; P p7 = delegate(int[] a) // Error, return type mismatch { if (a.Length > 0) return a[0]; return "Hello"; }; delegate object Q(params int[] a); Q q1 = delegate(int[] a) // Ok { if (a.Length > 0) return a[0]; return "Hello"; };
end 示例
示例:后面的示例使用泛型委托类型
Func<A,R>
,该类型表示采用类型A
参数并返回类型值的R
函数:delegate R Func<A,R>(A arg);
在工作分配中
Func<int,int> f1 = x => x + 1; // Ok Func<int,double> f2 = x => x + 1; // Ok Func<double,int> f3 = x => x + 1; // Error Func<int, Task<int>> f4 = async x => x + 1; // Ok
每个匿名函数的参数和返回类型从分配匿名函数的变量的类型确定。
第一个赋值成功将匿名函数转换为委托类型
Func<int,int>
,因为当给定类型int
时x
,x + 1
是隐式转换为类型的int
有效表达式。同样,第二个赋值成功将匿名函数转换为委托类型 Func<int,double>,因为(类型
int
)的结果x + 1
隐式转换为类型double
。但是,第三个赋值是编译时错误,因为当给定类型时
x
,(类型double
)的结果x + 1
无法隐式转换为类型int
double
。第四个赋值成功将匿名异步函数转换为委托类型
Func<int, Task<int>>
,因为(类型int
)的结果x + 1
可隐式转换为具有返回类型的异步 lambda 的有效返回类型int
Task<int>
。end 示例
如果F
与委托类型兼容,则 lambda 表达式F
与表达式树类型Expression<D>
D
兼容。 这不适用于匿名方法,仅适用于 lambda 表达式。
匿名函数可能会影响重载解析,并参与类型推理。 有关更多详细信息,请参阅 §12.6 。
10.7.2 匿名函数转换为委托类型的评估
将匿名函数转换为委托类型会生成一个委托实例,该实例引用匿名函数和在计算时处于活动状态的捕获的外部变量集(可能为空)。 调用委托时,将执行匿名函数的正文。 使用委托引用的捕获的外部变量集执行正文中的代码。 delegate_creation_expression(§12.8.17.6)可用作将匿名方法转换为委托类型的替代语法。
从匿名函数生成的委托的调用列表包含单个条目。 未指定委托的确切目标对象和目标方法。 具体而言,不指定委托的目标对象是 null
、 this
封闭函数成员的值还是其他一些对象。
允许将具有相同(可能为空)捕获的外部变量实例集的语义上相同的匿名函数转换为同一委托类型(但不是必需的)。 此处使用术语语义相同的表示,在所有情况下,匿名函数的执行都会产生相同的效果,因为参数相同。 此规则允许以下代码进行优化。
delegate double Function(double x);
class Test
{
static double[] Apply(double[] a, Function f)
{
double[] result = new double[a.Length];
for (int i = 0; i < a.Length; i++)
{
result[i] = f(a[i]);
}
return result;
}
static void F(double[] a, double[] b)
{
a = Apply(a, (double x) => Math.Sin(x));
b = Apply(b, (double y) => Math.Sin(y));
...
}
}
由于两个匿名函数委托具有相同(空)组捕获的外部变量,并且由于匿名函数在语义上是相同的,因此允许编译器让委托引用相同的目标方法。 事实上,允许编译器从这两个匿名函数表达式中返回完全相同的委托实例。
10.7.3 lambda 表达式转换为表达式树类型的计算
将 lambda 表达式转换为表达式树类型会生成表达式树(§8.6)。 更确切地说,lambda 表达式转换的计算会生成一个对象结构,该结构表示 lambda 表达式本身的结构。
并非每个 lambda 表达式都可以转换为表达式树类型。 转换为兼容的委托类型始终 存在,但出于实现定义的原因,在编译时可能会失败。
注意:lambda 表达式无法转换为表达式树类型的常见原因包括:
- 它具有块体
async
它具有修饰符- 它包含赋值运算符
- 它包含输出或引用参数
- 它包含动态绑定的表达式
end note
10.8 方法组转换
从方法组(§12.2)到兼容的委托类型(§20.4)存在隐式转换。 如果D
为委托类型,并且E
是一个分类为方法组的表达式,则E
D
仅当E
包含至少一个适合其正常形式(§12.6.4.2)的方法(§12.6.4.2)时兼容,其类型和修饰符与参数类型和修饰符D
匹配,如下所述。
下面介绍了从方法组 E
到委托类型的 D
转换的编译时应用程序。
- 选择与表单
E(A)
的方法调用(§12.8.10.2)对应的单个方法M
,并进行了以下修改:- 参数列表
A
是一个表达式列表,每个表达式都分类为变量,并且其类型与修饰符(或)的相应 parameter_listD
参数的类型和修饰符(out
in
或ref
)在类型(类型的参数除外dynamic
),其中相应的表达式具有类型而不是dynamic
类型object
。 - 考虑的候选方法只是那些以正常形式适用的方法,不省略任何可选参数(§12.6.4.2)。 因此,如果候选方法仅适用于扩展形式,或者其一个或多个可选参数没有相应的参数,
D
则忽略候选方法。
- 参数列表
- 如果 §12.8.10.2 的算法生成与 兼容的单个最佳方法
M
,D
则认为存在转换。 - 如果所选方法是实例方法
M
,则与E
关联的实例表达式确定委托的目标对象。 - 如果所选方法是一个扩展方法,该扩展方法
M
由实例表达式上的成员访问表示,则该实例表达式确定委托的目标对象。 - 转换的结果是一个类型
D
值,即引用所选方法和目标对象的委托。
示例:下面演示了方法组转换:
delegate string D1(object o); delegate object D2(string s); delegate object D3(); delegate string D4(object o, params object[] a); delegate string D5(int i); class Test { static string F(object o) {...} static void G() { D1 d1 = F; // Ok D2 d2 = F; // Ok D3 d3 = F; // Error – not applicable D4 d4 = F; // Error – not applicable in normal form D5 d5 = F; // Error – applicable but not compatible } }
要隐式将方法组
F
转换为类型的D1
值的赋d1
值。用于
d2
演示如何创建对方法的委托,该方法的派生(逆变)参数类型和更派生的(协变)返回类型。用于显示此方法不适用时不存在转换的赋值
d3
。用于
d4
显示方法如何以正常形式应用的方法的赋值。用于
d5
显示委托和方法的参数和返回类型如何只允许对引用类型有所不同的赋值。end 示例
与所有其他隐式转换和显式转换一样,强制转换运算符可用于显式执行特定转换。
示例:因此,示例
object obj = new EventHandler(myDialog.OkClick);
可以改为写入
object obj = (EventHandler)myDialog.OkClick;
end 示例
方法组转换可以通过显式指定类型参数(§12.6.3)E
来引用泛型方法。 如果使用类型推理,则委托的参数类型在推理过程中用作参数类型。 委托的返回类型不用于推理。 无论是指定还是推断类型参数,它们都是方法组转换过程的一部分;这些是调用生成的委托时用于调用目标方法的类型参数。
示例:
delegate int D(string s, int i); delegate int E(); class X { public static T F<T>(string s, T t) {...} public static T G<T>() {...} static void Main() { D d1 = F<int>; // Ok, type argument given explicitly D d2 = F; // Ok, int inferred as type argument E e1 = G<int>; // Ok, type argument given explicitly E e2 = G; // Error, cannot infer from return type } }
end 示例
方法组可能会影响重载解析,并参与类型推理。 有关更多详细信息,请参阅 §12.6 。
方法组转换的运行时评估按如下所示进行:
- 如果在编译时选择的方法是实例方法,或者它是作为实例方法访问的扩展方法,则委托的目标对象取决于与
E
以下关联的实例表达式:- 计算实例表达式。 如果此评估导致异常,则不会执行进一步的步骤。
- 如果实例表达式是 reference_type,则实例表达式计算的值将成为目标对象。 如果所选方法是实例方法,并且目标对象为
null
,则会引发 a,并且不执行进一System.NullReferenceException
步的步骤。 - 如果实例表达式是 value_type,则执行装箱操作(§10.2.9),以将值转换为对象,此对象将成为目标对象。
- 否则,所选方法是静态方法调用的一部分,委托的目标对象为
null
。 - 通过对编译时确定的方法的引用以及对上面计算的目标对象的引用,获取委托类型的
D
委托实例,如下所示:- 允许转换(但不是必需的)以使用已包含这些引用的现有委托实例。
- 如果未重复使用现有实例,则会创建一个新实例(§20.5)。 如果没有足够的可用内存来分配新实例,则会引发一个
System.OutOfMemoryException
。 否则,使用给定引用初始化实例。