10 个转换

10.1 常规

转换会导致表达式转换为特定类型或被视为特定类型的表达式;在前例中,转换可能涉及表示形式的更改。 转换可以是 隐式 转换, 也可以是显式转换,这确定是否需要显式强制转换。

例如:例如,从类型到类型的intlong转换是隐式的,因此可以隐式地将类型的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 常规

以下转换被归类为隐式转换:

隐式转换可能发生在各种情况下,包括函数成员调用(§12.6.6)、强制转换表达式(§12.9.7)和赋值(§12.21)。

预定义的隐式转换始终成功,永远不会引发异常。

注意:正确设计的用户定义的隐式转换也应表现出这些特征。 end note

出于转换目的,类型和objectdynamic标识可转换(§10.2.2)。

但是,动态转换(§10.2.10)仅适用于类型 dynamic§8.2.4)的表达式。

10.2.2 标识转换

标识转换从任何类型转换为同一类型或运行时等效的类型。 这种转换存在的原因之一是,可以 T 说类型的类型或表达式 T 可以转换为 T 自身。 存在以下标识转换:

  • 对于任何类型T,介于TT之间。
  • 介于和引用类型之间 TT? 用于任何引用类型 T
  • 介于 objectdynamic.
  • 当每个对应元素类型对之间存在标识转换时,在具有相同的 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。 元组元素类型可能由元组本身,如中t4t5t6。 每个对应的元素类型(包括嵌套元组)之间存在标识转换,因此元组 t4类型之间存在标识转换, t5以及 t6

end 示例

所有标识转换都是对称的。 如果标识转换存在自T₁T₂,则标识转换存在自该转换T₂T₁ 当标识转换存在于两种类型之间时,这两种类型是 可转换 的标识。

在大多数情况下,标识转换在运行时不起作用。 但是,由于浮点运算的精度可能高于其类型(§8.3.7)规定的精度,因此其结果赋值可能会导致精度损失,并且可以保证显式强制转换将精度降低到类型规定的精度(§12.9.7)。

10.2.3 隐式数值转换

隐式数值转换包括:

  • sbyte到、intlongfloatdoubledecimalshort
  • byteushort、、intuintlongulongfloat、或doubledecimalshort
  • shortintlongfloatdoubledecimal
  • ushortuintlongulongfloat、或doubledecimalint
  • From int tolongfloatdoubleor.decimal
  • uintlongulongfloatdoubledecimal
  • longfloatdoubledecimal
  • ulongfloatdoubledecimal
  • charushortintuintlongulongfloat、或doubledecimal
  • floatdouble

intlong uintulong转换到或从doublelongulong转换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.IFormattableinterpolated_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_typeobjectdynamic
  • 从任何class_typeS到任何class_typeT,提供的S派生自 T
  • 从任何class_typeS到任何interface_typeT,都提供了S实现T
  • 从任何interface_typeS到任何interface_typeT,提供的S派生自 T
  • 从具有元素类型的array_typeS具有元素类型的SᵢTᵢarray_typeT,前提是以下所有内容均为 true:
    • S 并且 T 仅在元素类型中不同。 换句话说, S 维度 T 数相同。
    • 隐式引用转换从 <a0/> 到 Tᵢ.
  • 从单维数组类型S[]System.Collections.Generic.IReadOnlyList<T>System.Collections.Generic.IList<T>基接口,前提是从隐式标识或引用转换ST
  • 从任何 array_typeSystem.Array 它实现的接口。
  • 从任何 delegate_typeSystem.Delegate 它实现的接口。
  • 从 null 文本(§6.4.5.7)到任何引用类型。
  • T 如果reference_type具有隐式标识或对reference_type的引用转换,并且T₀标识转换到Treference_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_typeI,以便从non_nullable_value_type转换为另一个interface_typeI₀,并I₀具有标识转换。I
  • 从任何non_nullable_value_type到任何interface_typeI,使拳击从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();
    }
}

现在,装箱一个类型的Sv包括执行表达式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,因为分配pbox中发生的隐式装箱操作会导致复制值p。 如果 Point 已改为声明 class 值 20,则输出值 20,因为 pbox 引用同一实例。

拳击类的类比不应用作一个有用的工具,用于说明拳击在概念上的工作原理。 此规范描述的行为与以这种方式精确实现装箱的行为之间存在许多细微差异。

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

对操作的绑定 s2i 都采用隐式动态转换,其中操作的绑定在运行时挂起。 在运行时,从 (string) 的d运行时类型到目标类型寻求隐式转换。 找到转换到 string 但不转换为 int

end 示例

10.2.11 隐式常量表达式转换

隐式常量表达式转换允许以下转换:

  • 类型constant_expression§12.23int可以转换为类型sbytebyte、、ushortshort、或uintulongconstant_expression的值在目标类型范围内。
  • 类型constant_expression可以转换为类型ulonglong,前提是constant_expression的值不是负值。

10.2.12 涉及类型参数的隐式转换

对于已知为引用类型的type_parameterT§15.2.5),存在以下隐式引用转换(§10.2.8):

  • T 其有效的基类 C,从 T 任何基类,从任何基类 C,从任何 T 接口实现 C
  • Tinterface_typeTI的有效接口集和从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.ObjectSystem.ValueType或者 System.Enum (否则 T 已知为引用类型)。 end note

  • Tinterface_typeTI的有效接口集和从T任何基接口。I

对于未知为引用类型的type_parameter T ,提供的T类型参数U有隐式转换T取决于U。 在运行时,如果是 T 值类型并且 U 是引用类型,则转换将作为装箱转换执行。 在运行时,如果两者都是TU值类型,则T并且U一定是相同的类型,并且不执行任何转换。 在运行时,如果是 T 引用类型,则 U 一定也是引用类型,并且转换作为隐式引用转换或标识转换(§15.2.5)执行。

给定类型参数 T存在以下进一步隐式转换:

  • 如果引用类型具有对引用类型的隐式转换,并且具有标识转换到的引用类型,则从T引用类型SS引用类型S₀S₀ 在运行时,转换的执行方式与转换方式 S₀相同。
  • 如果T接口类型具有对接口类型的II₀隐式转换,并且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

t1t4 t2声明和t5所有有效,因为从元素表达式到相应的元素类型存在隐式转换。 声明 t3 无效,因为没有从中 null 转换到 intt5声明会导致警告,因为元组表达式中的元素名称不同于元组类型中的元素名称。

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 常规

以下转换被归类为显式转换:

显式转换可以在强制转换表达式(§12.9.7)中发生。

显式转换集包括所有隐式转换。

注意:例如,允许在存在隐式标识转换时使用显式强制转换,以强制选择特定方法重载。 end note

非隐式转换的显式转换是无法证明始终成功的转换、已知可能丢失信息的转换,以及跨类型域的转换,与值得显式表示法完全不同的转换。

10.3.2 显式数值转换

显式数值转换是从numeric_type转换为另一numeric_type,其中隐式数值转换(§10.2.3)尚不存在:

  • sbytebyteushortuintulongchar
  • byte /或 charsbyte .
  • short到、byteushortuintulongcharsbyte
  • From ushort tosbytebyteshortor.char
  • intbyteshortushortuint、或ulongcharsbyte
  • uint到、byteshortushortintcharsbyte
  • longsbytebyteshortushortintuint、或ulongchar
  • ulongsbytebyteshortushortintuint、或longchar
  • charsbytebyteshort
  • floatbyteshortushort、、intuintlongulong、或chardecimalsbyte
  • doublebyte、、shortushort、、intuintulonglong、、char、或floatdecimalsbyte
  • decimalbyte、、shortushort、、intuintulonglong、、char、或floatdoublesbyte

由于显式转换包括所有隐式和显式数值转换,因此始终可以使用强制转换表达式(§12.9.7)从任何numeric_type转换为任何其他numeric_type

显式数值转换可能会丢失信息,或者可能导致引发异常。 显式数值转换按如下方式进行处理:

  • 对于从整型类型到另一个整型类型的转换,处理取决于发生转换的溢出检查上下文(§12.8.20):
    • checked在上下文中,如果源操作数的值在目标类型范围内,则转换成功,但如果源操作数的值超出目标类型的范围,则会引发该System.OverflowException转换。
    • unchecked在上下文中,转换始终成功,并继续如下。
      • 如果源类型大于目标类型,则通过放弃其“额外”最重要的位来截断源值。 结果会被视为目标类型的值。
      • 如果源类型的大小与目标类型相同,则源值被视为目标类型的值
  • 对于从 decimal 整型到整型的转换,源值将舍入为零到最接近的整型值,此整型值将成为转换的结果。 如果生成的整型值超出目标类型的范围,则会引发 a System.OverflowException
  • 对于从 floatdouble 转换为整型类型,处理取决于发生转换的溢出检查上下文(§12.8.20):
    • 在选中的上下文中,转换将按如下所示进行:
      • 如果操作数的值为 NaN 或无限,则会引发 a System.OverflowException
      • 否则,源操作数将舍入为零到最接近的整数值。 如果此整型值在目标类型的范围内,则此值是转换的结果。
      • 否则,将会引发 System.OverflowException
    • 在未选中的上下文中,转换始终成功,并继续执行如下操作。
      • 如果操作数的值为 NaN 或无限,则转换的结果是目标类型的未指定值。
      • 否则,源操作数将舍入为零到最接近的整数值。 如果此整型值在目标类型的范围内,则此值是转换的结果。
      • 否则,转换的结果是目标类型的未指定值。
  • 对于从中 double 转换到 float的值,该值 double 将舍入为最接近 float 的值。 double如果值太小而无法表示为 afloat,则结果将变为零,其符号与值相同。 如果值的大小 double 太大而无法表示为 a float,则结果将变为无穷大,其符号与值相同。 double如果值为 NaN,则结果也是 NaN。
  • 对于从float或转换到decimal的转换,源值将转换为decimal表示形式,并在需要时舍入到最接近的数字(§8.3.8double)。
    • 如果源值太小而无法表示为 a decimal,则结果为零,如果 decimal 支持有符号零值,则保留原始值的符号。
    • 如果源值的大小太大而无法表示为 decimal无穷大,或者该值为无穷大,则结果为无限保留原始值的符号(如果小数表示形式支持无数);否则将引发 System.OverflowException。
    • 如果源值为 NaN,则如果十进制表示形式支持 NaN,则结果为 NaN;否则将引发 System.OverflowException。
  • 对于从或转换decimalfloat该值decimal舍入为最接近double的值或float值。double 如果源值的大小太大,无法在目标类型中表示,或者该值为无穷大,则结果将无限保留原始值的符号。 如果源值为 NaN,则结果为 NaN。 虽然此转换可能会丢失精度,但它永远不会引发异常。

注意:类型 decimal 不是支持无限值或 NaN 值所必需的,但可能这样做;其范围可能小于 float 范围, double但不能保证是。 对于decimal没有无限值或 NaN 值的表示形式,并且范围小于float,则从decimal任一转换到任floatdouble一值或永远不会是无穷大或 NaN 的结果。 end note

10.3.3 显式枚举转换

显式枚举转换为:

  • sbytebyteshortushortint、、uintulonglongcharfloat、或decimaldouble任意enum_type。
  • 从任何enum_typesbyte、、byteushortshortintuint、、longulongcharfloat、或doubledecimal
  • 从任何 enum_type 到任何其他 enum_type

将任何参与enum_type视为该enum_type的基础类型,然后在结果类型之间执行隐式或显式数值转换,来处理两种类型之间的显式枚举转换。

示例:给定一个具有和基础类型的intenum_typeE,从byteE到的转换将作为显式数值转换(§10.3.2)进行处理byteint,从其进行转换byteE作为隐式数值转换(§10.2.3)进行处理。byte int end 示例

10.3.4 显式可为 null 的转换

显式可为 null 转换是从显式和隐式预定义转换派生的那些可为 null 的转换(§10.6.1)。

10.3.5 显式引用转换

显式引用转换包括:

  • 从对象到任何其他 reference_type
  • 从任何class_typeS到任何class_typeT,提供的S是基类T
  • 从任何class_typeS到任何interface_typeT,未S密封,并且未S实现T
  • 从任何interface_typeS到任何class_typeT,未T密封或提供T实现S
  • 从任何interface_typeS到任何interface_typeT,提供的S不是派生自 T
  • 从具有元素类型的array_typeS具有元素类型的SᵢTᵢarray_typeT,前提是以下所有内容均为 true:
    • S 并且 T 仅在元素类型中不同。 换句话说, S 维度 T 数相同。
    • 显式引用转换从 <a0/> 到 Tᵢ.
  • System.Array 它实现的接口到任何 array_type
  • 从单维array_typeS[]System.Collections.Generic.IList<T>System.Collections.Generic.IReadOnlyList<T>以及其基接口,前提是存在标识转换或从中显式引用转换ST
  • 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)。从引用类型ST₀接口或委托类型T T₀ T₀
  • D<S₁...Sᵥ>何处D<X₁...Xᵥ>是泛型委托类型,D<S₁...Sᵥ>与以下类型的每种类型参数DXᵢ不兼容或相同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相同,并且每个元素中的隐式或显式转换与 中的TE相应元素类型相同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_typeI到任何non_nullable_value_type,其中从interface_type到non_nullable_valueI₀类型的取消装箱转换,以及从I中转换到I₀的标识转换。
  • 从任何interface_typeI到任何non_nullable_value_type,其中从interface_typeI₀non_nullable_value_typeI₀的取消装箱转换,要么variance_convertible I I或可转换为方差(I₀§18.2.3.3)。
  • 从任何reference_type到从reference_typenullable_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_typeS由执行表达式((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的装箱值引用。 如果引发源操作数nullSystem.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):

  • 从有效基类CTT /从任何基类CT.
  • 从任何 interface_typeT
  • T到提供的任何interface_typeI,还没有从隐式引用转换TI
  • 从type_parameterU提供U T T取决于 (§15.2.5) 。

    注意:由于 T 已知是引用类型,因此在运行时类型中 T,运行时类型将始终是引用类型,即使 U 不知道在编译时是引用类型。 end note

对于未知为引用类型的type_parameterT§15.2.5),在编译时,涉及的以下转换T被视为取消装箱转换(§10.3.7)。 在运行时,如果 T 为值类型,则转换将作为取消装箱转换执行。 在运行时,如果是 T 引用类型,则转换将作为显式引用转换或标识转换执行。

  • 从有效基类CTT /从任何基类CT.

    注意:C 将是其中一种类型 System.ObjectSystem.ValueType或者 System.Enum (否则 T 已知为引用类型)。 end note

  • 从任何 interface_typeT

对于未知为引用类型的type_parameter T §15.2.5),存在以下显式转换:

  • T到任何interface_typeI提供,还没有从隐式转换TI。 此转换由隐式装箱转换(§10.2.9)组成objectT,后跟从显式引用转换objectI。 在运行时,如果 T 为值类型,则转换将作为装箱转换执行,后跟显式引用转换。 在运行时,如果是 T 引用类型,则转换将作为显式引用转换执行。
  • 从依赖于的类型参数U(§15.2.5)提供T UT 在运行时,如果是 T 值类型并且 U 是引用类型,则转换将作为取消装箱转换执行。 在运行时,如果两者都是TU值类型,则T并且U一定是相同的类型,并且不执行任何转换。 在运行时,如果是 T 引用类型,则 U 一定也是引用类型,并且转换作为显式引用转换或标识转换执行。

在所有情况下,规则确保转换作为取消装箱转换执行,前提是在运行时转换是从引用类型转换为值类型。

上述规则不允许从不受约束的类型参数直接转换为非接口类型,这可能令人吃惊。 此规则的原因是防止混淆,并明确此类转换的语义。

示例:请考虑以下声明:

class X<T>
{
    public static long F(T t)
    {
        return (long)t;         // Error
    }
}

如果允许直接显式转换 tlong 该转换,人们很容易期望 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 无法直接转换为 a long

end 示例

10.3.9 用户定义的显式转换

用户定义的显式转换由可选的标准显式转换组成,然后执行用户定义的隐式或显式转换运算符,后跟另一个可选的标准显式转换。 用于评估用户定义的显式转换的确切规则在 §10.5.5介绍。

10.4 标准转换

10.4.1 常规

标准转换是可以作为用户定义的转换的一部分发生的预定义转换。

10.4.2 标准隐式转换

以下隐式转换被归类为标准隐式转换:

标准隐式转换专门排除用户定义的隐式转换。

10.4.3 标准显式转换

标准显式转换都是标准隐式转换,以及存在相反标准隐式转换的显式转换的子集。

注意:换句话说,如果从类型到类型AB存在标准隐式转换,则从类型到类型以及从类型AB到类型BA存在标准显式转换。 end note

10.5 用户定义的转换

10.5.1 常规

C# 允许用户定义的转换扩充预定义的隐式和显式转换。 用户定义的转换是通过在类和结构类型中声明转换运算符 (§15.10.4)来引入的。

10.5.2 允许的用户定义转换

C# 仅允许声明某些用户定义的转换。 具体而言,无法重新定义现有的隐式或显式转换。

对于给定的源类型和S目标类型,如果ST为可为 null 的值类型T,请让S₀T₀引用其基础类型,否则S₀,它们T₀分别等于ST分别。 仅当以下所有内容均为 true 时,才允许类或结构声明从源类型 S 转换为目标类型 T

  • S₀T₀ 不同类型的。
  • S₀ T₀运算符声明所在的类或结构类型。
  • S₀ T₀ 不是interface_type。
  • 排除用户定义的转换,转换不存在于S或从TT转换S

适用于用户定义的转换的限制在 §15.10.4指定。

10.5.3 用户定义的转换评估

用户定义的转换将源表达式(可能具有源类型)转换为另一种类型,称为目标类型 用户定义转换的评估侧重于查找 源表达式和目标类型的最具体的 用户定义的转换运算符。 此决定分为几个步骤:

  • 查找将从中考虑用户定义的转换运算符的类和结构集。 如果源类型存在,则此集由源类型及其基类以及目标类型和基类组成。 为此,假定只有类和结构可以声明用户定义的运算符,并且非类类型没有基类。 此外,如果源类型或目标类型为 nullable-value-type,则改用其基础类型。
  • 从该类型集中,确定哪些用户定义的和提升转换运算符适用。 要使转换运算符适用,可以从源表达式执行从源表达式到运算符操作数类型的标准转换(§10.4),并且可以执行从运算符的结果类型到目标类型的标准转换。
  • 从一组适用的用户定义的运算符中,确定哪个运算符最明确。 一般情况下,最具体的运算符是操作数类型与源表达式“最接近”的运算符,其结果类型与目标类型“最接近”。 用户定义的转换运算符优先于提升的转换运算符。 用于建立最具体的用户定义的转换运算符的确切规则在以下子项中定义。

确定最具体的用户定义转换运算符后,用户定义转换的实际执行最多涉及三个步骤:

  • 首先,如果需要,执行从源表达式到用户定义或提升转换运算符的操作数类型的标准转换。
  • 接下来,调用用户定义的或提升转换运算符来执行转换。
  • 最后,如果需要,请执行从用户定义的转换运算符的结果类型到目标类型的标准转换。

用户定义转换的计算绝不涉及多个用户定义的或提升转换运算符。 换句话说,从类型S到类型的T转换永远不会首先执行用户定义的转换SX然后执行用户定义的转换。X T

  • 以下子集提供了用户定义隐式转换或显式转换评估的确切定义。 这些定义使用以下术语:
  • 如果标准隐式转换(§10.4.2)从类型A到类型B存在,如果两A者都不Binterface_typesA则表示B包含,据说B包含。 A
  • 如果标准隐式转换(§10.4.2)从表达式E到一种类型B存在,并且(如果B它有一个)的类型都没有Einterface_types,则E据说由它包含B,并且B据说包含E
  • 组类型中最包含的类型 是包含集中所有其他类型的一种类型。 如果没有单一类型包含所有其他类型,则集没有最包含的类型。 在更直观的术语中,最包含的类型是集中的“最大”类型,即可以隐式转换其他每种类型的类型。
  • 组类型中最包含的类型 是集中所有其他类型所包含的一种类型。 如果所有其他类型均未包含任何单个类型,则集没有最包含的类型。 在更直观的术语中,最包含的类型是集中的“最小”类型,即可以隐式转换为其他每种类型的类型。

10.5.4 用户定义的隐式转换

将用户定义的隐式转换从表达式 E 转换为类型 T ,如下所示:

  • 确定类型 SS₀ 以及 T₀

    • 如果有 E 类型,请将其 S 指定为该类型。
    • 如果ST为可以为 null 的值类型,则让SᵢTᵢ其基础类型,否则分别让SᵢTᵢST
    • 如果 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 该转换。

如果用户定义的隐式转换从类型S到类型T存在,则存在从类型变量到T该类型的S隐式转换。

10.5.5 用户定义的显式转换

将用户定义的显式转换从表达式 E 转换为类型 T ,如下所示:

  • 确定类型 SS₀ 以及 T₀
    • 如果有 E 类型,请将其 S 指定为该类型。
    • 如果ST为可以为 null 的值类型,则让SᵢTᵢ其基础类型,否则分别让SᵢTᵢST
    • 如果 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。 如果找不到完全包含的一种类型,则转换不明确,并且会发生编译时错误。
  • 在以下项中找到U最具体的目标类型Tₓ
    • 如果转换到T的任何运算符U,则TₓT
    • 否则,如果任何运算符 U 转换为所 T包含的类型,则 Tₓ 这些运算符的组合目标类型集中最包含的类型。 如果找不到完全包含的一种类型,则转换不明确,并且会发生编译时错误。
    • 否则, Tₓ 是运算符的一组组合目标类型中最包含的类型 U。 如果找不到最包含的类型,则转换不明确,并且会发生编译时错误。
  • 查找最具体的转换运算符:
    • 如果 U 仅包含一个从 Sₓ 中转换到 Tₓ的用户定义转换运算符,则这是最具体的转换运算符。
    • 否则,如果 U 正好包含一个从 Sₓ 中转换到 Tₓ的提升转换运算符,则这是最具体的转换运算符。
    • 否则,转换不明确且发生编译时错误。
  • 最后,应用转换:
    • 如果 E 还没有该类型 Sₓ,则执行从 E 到 Sₓ 的标准显式转换。
    • 调用最具体的用户定义的转换运算符以从 Sₓ 中转换到 Tₓ
    • T如果没有Tₓ,则执行从标准显式转换TₓT该转换。

如果用户定义的显式转换从类型到类型ST存在,则存在从类型到TS变量的用户定义的显式转换。

涉及可为 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?
  • 从隐式或显式转换 ST?
  • 从显式转换 S?T.

可为 null 的转换本身被归类为隐式或显式转换。

某些可为 null 的转换被归类为标准转换,并可以作为用户定义的转换的一部分进行。 具体而言,所有隐式可为 null 转换都归类为标准隐式转换(§10.4.2),满足 §10.4.3 要求的显式可为 null 转换被归类为标准显式转换。

基于基础转换从基础转换到T收益计算可为 null 的转换S,如下所示:

  • 如果可为 null 的转换从以下值 S? 转换为 T?
    • 如果源值为 null(HasValue 属性为 false),则结果为类型 T?为 null 值。
    • 否则,将转换计算为从中解包S?,后跟基础转换ST然后从中换行T?TS
  • 如果可为 null 的转换是从ST?的,则将转换计算为基础转换,ST后跟从换T行。T?
  • 如果可为 null 的转换是从S?T的,则将转换计算为从中解包S?S然后从基础转换到T基础转换S

10.6.2 提升转换

给定用户定义的转换运算符,该运算符从不可为 null 的值类型转换为不可为 null 的值类型ST则存在从S?中转换到T?的提升转换运算符。 此提升转换运算符执行从用户定义转换到S后跟换行T?T的解包S?,但 null 值S?直接转换为 null 值T?T S 提升的转换运算符与其基础用户定义的转换运算符具有相同的隐式或显式分类。

10.7 匿名函数转换

10.7.1 常规

anonymous_method_expressionlambda_expression被归类为匿名函数(§12.19)。 表达式没有类型,但可以隐式转换为兼容的委托类型。 某些 lambda 表达式也可能隐式转换为兼容的表达式树类型。

具体而言,匿名函数 F 与提供的委托类型 D 兼容:

  • 如果 F 包含 anonymous_function_signature,则 D 具有相同 F 数量的参数。
  • 如果 F 不包含 anonymous_function_signature,则 D 只要没有参数是输出参数,就可能具有任何类型的零个或多个参数 D
  • 如果F具有显式类型化参数列表,则每个参数的D修饰符与相应的参数相同,并且存在相应参数FF之间的标识转换。
  • 如果 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»返回类型,则当给定相应参数FD的类型时,F正文是有效块(w.r.t §13.3),其中没有return语句指定表达式。
  • 如果正文F是表达式,要么F是非异步类型,要么Dvoid非返回类型T,要么F是异步的,并且D具有«TaskType»<T>返回类型(§15.15.1),则当给定相应参数DF的类型时,正文F是可隐式转换为T的有效表达式(w.r.t §12)。
  • 如果主体F是块,要么F是非异步的,并且D具有非空返回类型T或者是F异步的,并且D具有«TaskType»<T>返回类型,则当每个参数的类型都给定相应参数FD的类型时,正文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>,因为当给定类型intxx + 1是隐式转换为类型的int有效表达式。

同样,第二个赋值成功将匿名函数转换为委托类型 Func<int,double>,因为(类型int)的结果x + 1隐式转换为类型double

但是,第三个赋值是编译时错误,因为当给定类型时x,(类型double)的结果x + 1无法隐式转换为类型intdouble

第四个赋值成功将匿名异步函数转换为委托类型Func<int, Task<int>>,因为(类型int)的结果x + 1可隐式转换为具有返回类型的异步 lambda 的有效返回类型intTask<int>

end 示例

如果F与委托类型兼容,则 lambda 表达式F与表达式树类型Expression<D>D兼容。 这不适用于匿名方法,仅适用于 lambda 表达式。

匿名函数可能会影响重载解析,并参与类型推理。 有关更多详细信息,请参阅 §12.6

10.7.2 匿名函数转换为委托类型的评估

将匿名函数转换为委托类型会生成一个委托实例,该实例引用匿名函数和在计算时处于活动状态的捕获的外部变量集(可能为空)。 调用委托时,将执行匿名函数的正文。 使用委托引用的捕获的外部变量集执行正文中的代码。 delegate_creation_expression§12.8.17.6)可用作将匿名方法转换为委托类型的替代语法。

从匿名函数生成的委托的调用列表包含单个条目。 未指定委托的确切目标对象和目标方法。 具体而言,不指定委托的目标对象是 nullthis 封闭函数成员的值还是其他一些对象。

允许将具有相同(可能为空)捕获的外部变量实例集的语义上相同的匿名函数转换为同一委托类型(但不是必需的)。 此处使用术语语义相同的表示,在所有情况下,匿名函数的执行都会产生相同的效果,因为参数相同。 此规则允许以下代码进行优化。

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是一个分类为方法组的表达式,则ED仅当E包含至少一个适合其正常形式(§12.6.4.2)的方法(§12.6.4.2时兼容,其类型和修饰符与参数类型和修饰符D匹配,如下所述。

下面介绍了从方法组 E 到委托类型的 D 转换的编译时应用程序。

  • 选择与表单E(A)的方法调用(§12.8.10.2)对应的单个方法M,并进行了以下修改:
    • 参数列表A是一个表达式列表,每个表达式都分类为变量,并且其类型与修饰符(或)的相应 parameter_listD参数的类型和修饰符(outinref)在类型(类型的参数除外dynamic),其中相应的表达式具有类型而不是dynamic类型object
    • 考虑的候选方法只是那些以正常形式适用的方法,不省略任何可选参数(§12.6.4.2)。 因此,如果候选方法仅适用于扩展形式,或者其一个或多个可选参数没有相应的参数, D则忽略候选方法。
  • 如果 §12.8.10.2算法生成与 兼容的单个最佳方法MD则认为存在转换。
  • 如果所选方法是实例方法 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.3E来引用泛型方法。 如果使用类型推理,则委托的参数类型在推理过程中用作参数类型。 委托的返回类型不用于推理。 无论是指定还是推断类型参数,它们都是方法组转换过程的一部分;这些是调用生成的委托时用于调用目标方法的类型参数。

示例:

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 。 否则,使用给定引用初始化实例。