Visual Basic 中的转换

转换是将值从一种类型更改为另一种类型的过程。 例如,可以将类型的值转换为类型的IntegerDouble值,也可以将类型的值Derived转换为类型Base值,假设DerivedBase既是类又Derived继承自Base。 转换可能不需要值本身更改(如后一个示例中),或者它们可能需要对值表示形式(如前一个示例中)进行重大更改。

转换可能正在扩大或缩小。 扩大转换是从类型转换为另一种类型,其值域至少比原始类型的值域大(如果不是大)。 扩大转换绝不会失败。 缩小转换是从类型转换为另一种类型,其值域小于原始类型的值域,或者足够无关,在执行转换时(例如,从转换IntegerString时)时必须格外小心。 缩小转换可能会失败,这可能会导致信息丢失。

标识转换(即从类型转换为自身)和默认值转换(即转换自 Nothing)针对所有类型定义。

隐式和显式转换

转换可以是 隐式 转换,也可以 是显式转换。 隐式转换在没有任何特殊语法的情况下进行。 下面是将值Long隐式转换为Integer值的示例:

Module Test
    Sub Main()
        Dim intValue As Integer = 123
        Dim longValue As Long = intValue

        Console.WriteLine(intValue & " = " & longValue)
    End Sub
End Module

另一方面,显式转换需要强制转换运算符。 尝试对没有强制转换运算符的值执行显式转换会导致编译时错误。 以下示例使用显式转换将值转换为LongInteger值。

Module Test
    Sub Main()
        Dim longValue As Long = 134
        Dim intValue As Integer = CInt(longValue)

        Console.WriteLine(longValue & " = " & intValue)
    End Sub
End Module

隐式转换集取决于编译环境和 Option Strict 语句。 如果使用严格的语义,则只能隐式进行扩大转换。 如果使用了宽松语义,则所有扩大和缩小转换(换句话说,所有转换)都可能会隐式发生。

布尔转换

虽然 Boolean 不是数值类型,但它确实对数值类型进行缩小转换,就像它是枚举类型一样。 True文本将转换为255文本的 、Byte65535、for UShort4294967295UInteger18446744073709551615、for、for ULong和表达式-1SByteShortIntegerLongDecimalSingle和。Double False文本转换为文本0。 零数值转换为文本 False。 所有其他数值都转换为文本 True

从布尔值到字符串的收缩转换,转换为任一或System.Boolean.FalseString两者System.Boolean.TrueString。 还有一个从以下范围缩小的转换StringBoolean:如果字符串等于TrueStringFalseString(在当前区域性中不区分大小写),则它使用适当的值;否则,它将尝试将字符串分析为数值类型(如果可能,则为十六进制或八进制),并使用上述规则;否则将引发System.InvalidCastException

数值转换

类型、SByte、、ShortUShortIntegerUIntegerULong、、Long、、DecimalDoubleSingle以及所有枚举类型之间存在Byte数值转换。 转换时,枚举类型将被视为其基础类型。 转换为枚举类型时,源值不需要符合枚举类型中定义的值集。 例如:

Enum Values
    One
    Two
    Three
End Enum

Module Test
    Sub Main()
        Dim x As Integer = 5

        ' OK, even though there is no enumerated value for 5.
        Dim y As Values = CType(x, Values)
    End Sub
End Module

在运行时处理数值转换,如下所示:

  • 对于从数值类型到更广泛的数值类型的转换,值只是转换为更广泛的类型。 从 UInteger、、ULongIntegerLongDecimal转换到或Double舍入Single到最近的SingleDouble值。 虽然此转换可能会导致精度损失,但它永远不会造成数量级损失。

  • 对于从整型类型转换为另一个整型类型,或者DecimalDoubleSingle整型类型转换为整型类型,结果取决于整数溢出检查是否为:

    如果正在检查整数溢出:

    • 如果源是整型类型,则如果源参数在目标类型范围内,转换会成功。 如果源参数超出目标类型的范围,则转换将 System.OverflowException 引发异常。

    • 如果源是 SingleDouble或者 Decimal,源值向上或向下舍入到最近的整型值,并且此整型值将成为转换的结果。 如果源值同样接近两个整型值,则该值将舍入为具有最小有效位数位置的偶数的值。 如果生成的整型值超出目标类型的范围, System.OverflowException 则会引发异常。

    如果未检查整数溢出:

    • 如果源是整型类型,则转换始终成功,只需放弃源值最重要的位即可。

    • 如果源是 SingleDouble或者 Decimal,转换始终成功,并且只是将源值舍入到最接近的整数值。 如果源值同样接近两个整型值,则该值始终舍入为处于最小有效位数位置的偶数的值。

  • 对于从中 Double 转换到 Single的值,该值 Double 将舍入为最接近 Single 的值。 Double如果该值太小而无法表示为 aSingle,则结果将变为正零或负零。 Double如果值太大而无法表示为 aSingle,则结果将变为正无穷大或负无穷大。 如果值为 DoubleNaN则结果也是 NaN

  • 对于从Single或转换到Decimal的转换,源值将转换为Decimal表示形式,并在第 28 位小数后舍入为最接近Double的数字(如果需要)。 如果源值太小而无法表示为 a Decimal,则结果为零。 如果源值为 NaN、无穷大或太大而无法表示 DecimalSystem.OverflowException 则会引发异常。

  • 对于从中 Double 转换到 Single的值,该值 Double 将舍入为最接近 Single 的值。 Double如果该值太小而无法表示为 aSingle,则结果将变为正零或负零。 Double如果值太大而无法表示为 aSingle,则结果将变为正无穷大或负无穷大。 如果值为 DoubleNaN则结果也是 NaN

引用转换

引用类型可以转换为基类型,反之亦然。 从基类型转换为更派生的类型仅在运行时成功,如果要转换的值是 null 值、派生类型本身或更派生的类型。

类和接口类型可以强制转换为任何接口类型以及从任何接口类型转换。 如果涉及的实际类型具有继承或实现关系,则类型与接口类型之间的转换仅在运行时成功。 由于接口类型将始终包含派生自 Object的类型实例,因此接口类型也可以始终强制转换为和从 Object中转换。

注意。 将类转换为它未实现的接口和从接口转换 NotInheritable 不是错误,因为表示 COM 类的类在运行时之前可能具有未知的接口实现。

如果在运行时引用转换失败, System.InvalidCastException 则会引发异常。

引用方差转换

泛型接口或委托可能具有变体类型参数,这些参数允许在类型的兼容变体之间进行转换。 因此,在运行时,从类类型或接口类型转换为与它继承或实现的接口类型兼容的接口类型将成功。 同样,可以将委托类型强制转换为与变体兼容的委托类型。 例如,委托类型

Delegate Function F(Of In A, Out R)(a As A) As R

将允许从中 F(Of Object, Integer) 转换到 F(Of String, Integer)。 也就是说,接受的委托F可以安全地用作接受String的委托FObject 调用委托时,目标方法需要对象,字符串是对象。

泛型委托或接口类型 S(Of S1,...,Sn) 称为与泛型接口或委托类型 兼容的变体T(Of T1,...,Tn) 如果:

  • S 并且 T 都是从同一泛型类型 U(Of U1,...,Un)构造的。

  • 对于每种类型参数 Ux

    • 如果类型参数声明时没有方差,Tx则必须Sx是同一类型。

    • 如果声明In了类型参数,则必须有一个扩大的标识、默认、引用、数组或类型参数转换SxTx

    • 如果声明Out了类型参数,则必须有一个扩大的标识、默认、引用、数组或类型参数转换TxSx

从类转换为具有变体类型参数的泛型接口时,如果类实现多个变体兼容接口,则转换不明确(如果没有非变体转换)。 例如:

Class Base
End Class

Class Derived1
    Inherits Base
End Class

Class Derived2
    Inherits Base
End Class

Class OneAndTwo
    Implements IEnumerable(Of Derived1)
    Implements IEnumerable(Of Derived2)
End Class

Class BaseAndOneAndTwo
    Implements IEnumerable(Of Base)
    Implements IEnumerable(Of Derived1)
    Implements IEnumerable(Of Derived2)
End Class

Module Test
    Sub Main()
        ' Error: conversion is ambiguous
        Dim x As IEnumerable(Of Base) = New OneAndTwo()

        ' OK, will pick up the direct implementation of IEnumerable(Of Base)
        Dim y as IEnumerable(Of Base) = New BaseAndOneAndTwo()
    End Sub
End Module

匿名委托转换

当分类为 lambda 方法的表达式在没有目标类型(例如 Dim x = Function(a As Integer, b As Integer) a + b)或目标类型不是委托类型的上下文中重新分类为值时,生成的表达式的类型是等效于 lambda 方法签名的匿名委托类型。 此匿名委托类型可转换为任何兼容的委托类型:兼容的委托类型是可以使用委托创建表达式和匿名委托类型的方法作为参数创建的任何委托类型 Invoke 。 例如:

' Anonymous delegate type similar to Func(Of Object, Object, Object)
Dim x = Function(x, y) x + y

' OK because delegate type is compatible
Dim y As Func(Of Integer, Integer, Integer) = x

请注意,这些类型 System.Delegate 本身 System.MulticastDelegate 并不被视为委托类型(即使所有委托类型都继承自它们)。 此外,请注意,从匿名委托类型到兼容委托类型的转换不是引用转换。

数组转换

除了在数组上定义的转换之外,由于它们是引用类型,数组还存在几个特殊的转换。

对于任何两种类型AB,如果它们都是值类型未知的引用类型或类型参数,并且如果A具有引用、数组或类型参数转换到B,则存在从类型A数组到具有相同排名的类型B数组的转换。 此关系称为 数组协变。 数组协变尤其意味着,其元素类型B实际上可以是其元素类型的A数组的元素,前提是该元素类型为AB引用类型且B具有引用转换或数组转换到的A数组的元素。 在以下示例中,第二次调用F会导致引发异常,因为实际元素类型bString,而不是ObjectSystem.ArrayTypeMismatchException

Module Test
    Sub F(ByRef x As Object)
    End Sub

    Sub Main()
        Dim a(10) As Object
        Dim b() As Object = New String(10) {}
        F(a(0)) ' OK.
        F(b(1)) ' Not allowed: System.ArrayTypeMismatchException.
   End Sub
End Module

由于数组协变,对引用类型数组元素的赋值包括运行时检查,以确保分配给数组元素的值实际上是允许的类型。

Module Test
    Sub Fill(array() As Object, index As Integer, count As Integer, _
            value As Object)
        Dim i As Integer

        For i = index To (index + count) - 1
            array(i) = value
        Next i
    End Sub

    Sub Main()
        Dim strings(100) As String

        Fill(strings, 0, 101, "Undefined")
        Fill(strings, 0, 10, Nothing)
        Fill(strings, 91, 10, 0)
    End Sub
End Module

在此示例中,对 array(i) 方法 Fill 中的赋值隐式包含运行时检查,确保变量 value 引用的对象是 Nothing 与数组 array的实际元素类型兼容的类型实例。 在方法 Main中,方法 Fill 的前两个调用成功,但第三个调用会导致在执行第一个 System.ArrayTypeMismatchException 赋值 array(i)时引发异常。 发生异常的原因是 Integer 无法存储在数组中 String

如果数组元素类型之一是类型参数,该类型在运行时变为值类型, System.InvalidCastException 则会引发异常。 例如:

Module Test
    Sub F(Of T As U, U)(x() As T)
        Dim y() As U = x
    End Sub

    Sub Main()
        ' F will throw an exception because Integer() cannot be
        ' converted to Object()
        F(New Integer() { 1, 2, 3 })
    End Sub
End Module

在枚举类型的数组和枚举类型的基础类型的数组或具有相同基础类型的另一个枚举类型的数组之间也存在转换,前提是数组具有相同的排名。

Enum Color As Byte
    Red
    Green
    Blue
End Enum

Module Test
    Sub Main()
        Dim a(10) As Color
        Dim b() As Integer
        Dim c() As Byte

        b = a    ' Error: Integer is not the underlying type of Color
        c = a    ' OK
        a = c    ' OK
    End Sub
End Module

在此示例中,一个数组Color被转换为和从基础类型的数组ByteColor。 但是,转换为数组 Integer将是一个错误,因为 Integer 不是基础类型 Color

类型的A()排名 1 数组还具有到集合接口类型的IList(Of B)数组转换, IReadOnlyList(Of B)IReadOnlyCollection(Of B)ICollection(Of B)并在IEnumerable(Of B)其中System.Collections.Generic找到,只要下列值之一为 true:

  • AB 是值类型未知的引用类型或类型参数,又 A 有一个扩大的引用、数组或类型参数转换为 B;或
  • A 并且 B 都是相同基础类型的枚举类型;或
  • 其中AB一种是枚举类型,另一种是其基础类型。

具有任何排名的 A 类型的任何数组也具有到非泛型集合接口类型的 IList数组转换, ICollection 并在 IEnumerable 其中 System.Collections找到。

可以使用或直接调用GetEnumerator方法循环访问生成的接口For Each。 对于转换泛型或非泛型形式的IListICollection排名 1 数组,也可以按索引获取元素。 对于转换为泛型或非泛型形式的 IList排名 1 数组,也可以按索引设置元素,但受上述相同运行时数组协变检查的约束。 所有其他接口方法的行为未由 VB 语言规范定义;它由基础运行时决定。

值类型转换

值类型值可以转换为其基引用类型之一,也可以转换为通过称为 装箱的进程实现的接口类型。 将值类型值装箱后,该值将从它所在的位置复制到 .NET Framework 堆上。 然后返回对堆上的此位置的引用,并可以存储在引用类型变量中。 此引用也称为值类型的 装箱 实例。 装箱实例的语义与引用类型相同,而不是值类型。

通过名为 “取消装箱”的进程,可以将装箱值类型转换回其原始值类型。 取消装箱值类型时,该值将从堆复制到变量位置。 从那一点开始,它的行为就像它是一个值类型。 取消装箱值类型时,该值必须是 null 值或值类型的实例。 否则 System.InvalidCastException 将引发异常。 如果该值是枚举类型的实例,则该值也可以取消装箱到枚举类型的基础类型或其他具有相同基础类型的枚举类型。 null 值被视为文本 Nothing

为了很好地支持可为 null 的值类型,在执行装箱和取消装箱时,将特别对待值类型 System.Nullable(Of T) 。 如果值的属性是True或值FalseNothingHasValue的属性为值,则装箱类型的Nullable(Of T)值将产生一个装箱值THasValue。 取消装箱类型的T值,Nullable(Of T)以生成其Value属性为装箱值且其属性为TrueHasValue实例Nullable(Of T)。 可以为任何TNothing取消装箱Nullable(Of T),并生成其HasValue属性为False的值。 由于装箱值类型的行为类似于引用类型,因此可以创建对相同值的多个引用。 对于基元类型和枚举类型,这无关紧要,因为这些类型的实例是不 可变的。 也就是说,无法修改这些类型的装箱实例,因此无法观察对相同值的多个引用。

另一方面,如果可访问其实例变量或者其方法或属性修改其实例变量,则结构可能可变。 如果使用对装箱结构的一个引用来修改结构,则对装箱结构的所有引用都将看到更改。 由于此结果可能是意外的,因此,当从 Object 一个位置复制到另一个装箱值类型的值时,将自动在堆上克隆值类型,而不只是复制其引用。 例如:

Class Class1
    Public Value As Integer = 0
End Class

Structure Struct1
    Public Value As Integer
End Structure

Module Test
    Sub Main()
        Dim val1 As Object = New Struct1()
        Dim val2 As Object = val1

        val2.Value = 123

        Dim ref1 As Object = New Class1()
        Dim ref2 As Object = ref1

        ref2.Value = 123

        Console.WriteLine("Values: " & val1.Value & ", " & val2.Value)
        Console.WriteLine("Refs: " & ref1.Value & ", " & ref2.Value)
    End Sub
End Module

程序的输出为:

Values: 0, 123
Refs: 123, 123

局部变量 val2 字段的赋值不会影响局部变量 val1 的字段,因为当已将装箱 Struct1 分配给 val2该字段时,将创建该值的副本。 相比之下,赋值 ref2.Value = 123 会影响同时 ref1 引用和 ref2 引用的对象。

注意。 无法对键入System.ValueType的装箱结构执行结构复制,因为无法延迟绑定。System.ValueType

对于在赋值时将复制装箱值类型的规则有一个例外。 如果装箱值类型引用存储在另一种类型中,则不会复制内部引用。 例如:

Structure Struct1
    Public Value As Object
End Structure

Module Test
    Sub Main()
        Dim val1 As Struct1
        Dim val2 As Struct1

        val1.Value = New Struct1()
        val1.Value.Value = 10

        val2 = val1
        val2.Value.Value = 123
        Console.WriteLine("Values: " & val1.Value.Value & ", " & _
            val2.Value.Value)
    End Sub
End Module

程序的输出为:

Values: 123, 123

这是因为在复制值时不会复制内部装箱值。 因此,同时val1.Valueval2.Value对同一装箱值类型具有引用。

注意。 未复制内部装箱值类型是 .NET 类型系统的一个限制 -- 以确保每当复制类型的 Object 值值时复制所有内部装箱值类型将极其昂贵。

如前所述,装箱值类型只能取消装箱到其原始类型。 但是,装箱基元类型在类型化 Object时会特别处理。 可以将其转换为任何其他基元类型,这些基元类型可以转换为它们。 例如:

Module Test
    Sub Main()
        Dim o As Object = 5
        Dim b As Byte = CByte(o)  ' Legal
        Console.WriteLine(b) ' Prints 5
    End Sub
End Module

通常,装箱值5无法取消装箱IntegerByte变量中。 但是,由于 Integer 基元类型且 Byte 具有转换,因此允许转换。

请务必注意,将值类型转换为接口不同于约束为接口的泛型参数。 当访问受约束类型参数(或调用方法) Object上的接口成员时,当值类型转换为接口并访问接口成员时,不会发生装箱。 例如,假设接口 ICounter 包含可用于修改值的方法 Increment 。 如果ICounter用作约束,则使用对调用的变量Increment的引用(而不是装箱副本)调用该方法的Increment实现:

Interface ICounter
    Sub Increment()
    ReadOnly Property Value() As Integer
End Interface

Structure Counter
    Implements ICounter

    Dim _value As Integer

    Property Value() As Integer Implements ICounter.Value
        Get
            Return _value
        End Get
    End Property

    Sub Increment() Implements ICounter.Increment
       value += 1
    End Sub
End Structure

Module Test
      Sub Test(Of T As ICounter)(x As T)
         Console.WriteLine(x.value)
         x.Increment()                     ' Modify x
         Console.WriteLine(x.value)
         CType(x, ICounter).Increment()    ' Modify boxed copy of x
         Console.WriteLine(x.value)
      End Sub

      Sub Main()
         Dim x As Counter
         Test(x)
      End Sub
End Module

第一次调用修改 Increment 变量 x中的值。 这与第二次调用 Increment 不等效,后者修改了 x 装箱副本中的值。 因此,程序的输出为:

0
1
1

可以为 Null 的值类型转换

值类型T可以转换为和从类型可以为 null 的版本。 T? 如果正在转换的值为 Nothing,则从T?中转换到T引发System.InvalidOperationException异常。 此外,T?如果T具有内部转换到S的类型,则具有对类型的S转换。 如果是S值类型,则以下内部转换存在于以下内部转换之间T?S?

  • 同一分类(缩小或扩大)的转换。T?S?

  • 同一分类(缩小或扩大)的转换。TS?

  • S? 缩小到 T的转换范围。

例如,内部扩展转换因Integer?Long?内部扩展转换存在从 :LongInteger

Dim i As Integer? = 10
Dim l As Long? = i

T?中转换到S?时,如果值为T?Nothing,则NothingS?值为 。 从S?T转换为S时,如果值为T?S?T?NothingSystem.InvalidCastException将引发异常。

由于基础类型 System.Nullable(Of T)的行为,当装箱可为 null 的值类型 T? 时,结果是一个装 T箱类型的值,而不是类型的 T?装箱值。 相反,当取消装箱到可以为 null 的值类型 T?时,该值将包装 System.Nullable(Of T)在一起,并将 Nothing 取消装箱到类型 T?为 null 的值。 例如:

Dim i1? As Integer = Nothing
Dim o1 As Object = i1

Console.WriteLine(o1 Is Nothing)                    ' Will print True
o1 = 10
i1 = CType(o1, Integer?)
Console.WriteLine(i1)                               ' Will print 10

此行为的副作用是,可以为 null 的值类型 T? 似乎可实现所有接口 T,因为将值类型转换为接口需要装箱类型。 因此, T? 可转换为可转换为的所有接口 T 。 但是,请务必注意,可为 null 的值类型 T? 实际上不会实现泛型约束检查或反射的接口 T 。 例如:

Interface I1
End Interface

Structure T1
    Implements I1
    ...
End Structure

Module Test
    Sub M1(Of T As I1)(ByVal x As T)
    End Sub

    Sub Main()
        Dim x? As T1 = Nothing
        Dim y As I1 = x                ' Valid
        M1(x)                          ' Error: x? does not satisfy I1 constraint
    End Sub
End Module

字符串转换

Char String转换为字符串的结果,其第一个字符是字符值。 String Char转换为一个字符,其值是字符串的第一个字符。 将数组 Char 转换为 String 一个字符串,其字符是数组的元素。 String Char转换为其元素为字符串字符的字符数组的结果数组。

与、、、、UIntegerUShortULongShortIntegerSByteSingleDoubleLongDecimal、、Date反之相反,之间的确切转换String超出了此规范的范围,并且依赖于实现,但一个细节除外。 ByteBoolean 字符串转换始终考虑运行时环境的当前区域性。 因此,必须在运行时执行它们。

扩大转换

扩大转换永远不会溢出,但可能需要丢失精度。 以下转换正在扩大转换:

标识/默认转换

  • 从类型到自身。

  • 从为 lambda 方法生成的匿名委托类型重新分类到具有相同签名的任何委托类型。

  • 从文本 Nothing 到类型。

数值转换

  • ByteUShort、、ShortUIntegerIntegerULongLong、或DecimalSingleDouble

  • SByteShortIntegerLongDecimalSingleDouble

  • UShortUIntegerIntegerULongLongDecimalSingleDouble

  • From Short toIntegerLongDecimalor.DoubleSingle

  • UIntegerULongLongDecimalSingleDouble

  • IntegerLongDecimalSingleDouble

  • ULongDecimalSingleDouble

  • From Long to DecimalSingle or Double.

  • DecimalSingleDouble

  • SingleDouble

  • 从文本 0 到枚举类型。 (注意。0任何枚举类型的转换正在扩大,以简化测试标志。例如,如果Values枚举类型具有值One,则可以通过说 .(v And Values.One) = 0) 来测试类型Values变量v

  • 从枚举类型到其基础数值类型,或从其基础数值类型具有扩大转换到的数字类型。

  • 如果常量表达式的值在目标类型范围内,则从类型ULongLong、、UIntegerUShortIntegerShort、或ByteSByte更窄的类型到常量表达式。 (注意。UIntegerInteger转换到SingleLongULong转换到SingleDecimalDouble转换为或Double可能导致Single精度损失,但永远不会造成数量级损失。其他扩大的数字转换永远不会丢失任何信息。

引用转换

  • 从引用类型到基类型。

  • 从引用类型到接口类型,前提是该类型实现接口或变体兼容接口。

  • 从接口类型到 Object.

  • 从接口类型到变体兼容的接口类型。

  • 从委托类型到变体兼容的委托类型。 (注意。 这些规则隐含了许多其他引用转换。例如,匿名委托是继承自 System.MulticastDelegate的引用类型;数组类型是继承自 System.Array的引用类型;匿名类型是继承自 System.Object.)

匿名委托转换

  • 从为 lambda 方法重新分类而生成的匿名委托类型到任何更广泛的委托类型。

数组转换

  • 从具有元素类型的数组类型SSe到具有元素类型的Te数组类型T,前提是以下所有内容均为 true:

    • S 并且 T 仅在元素类型中不同。

    • 两者都是SeTe引用类型,或者是已知为引用类型的类型参数。

    • 从 到 Te 存在Se扩大引用、数组或类型参数转换。

  • 从具有枚举元素类型的数组类型SSe到具有元素类型的数组类型TTe,前提是以下所有内容均为 true:

    • S 并且 T 仅在元素类型中不同。

    • Te 是基础类型的 Se

  • 从具有枚举元素类型的Se排名 1 的数组类型S,到System.Collections.Generic.IList(Of Te)IReadOnlyList(Of Te)ICollection(Of Te)IReadOnlyCollection(Of Te)IEnumerable(Of Te),如果下列值之一为 true:

    • 两者都是引用类型,或者是已知为引用类型的类型参数,并且存在从 <a0/> 到 的扩大引用、数组或类型参数转换

    • Te 是基础类型的 Se;或

    • TeSe

值类型转换

  • 从值类型到基类型。

  • 从值类型到类型实现的接口类型。

可以为 Null 的值类型转换

  • 从类型 T 到类型 T?

  • 从类型到类型T?S?,其中从类型到类型TS之间的扩展转换。

  • 从类型到类型TS?,其中从类型到类型TS之间的扩展转换。

  • 从类型 T? 到类型实现的 T 接口类型。

字符串转换

  • CharString

  • Char()String

类型参数转换

  • 从类型参数到 Object.

  • 从类型参数到接口类型约束或任何与接口类型约束兼容的接口变体。

  • 从类型参数到由类约束实现的接口。

  • 从类型参数到与类约束实现的接口兼容的接口变体。

  • 从类型参数到类约束,或类约束的基类型。

  • 从类型参数 T 到类型参数约束 Tx,或者任何内容 Tx 都有一个扩大的转换。

收缩转换

缩小转换是无法证明始终成功的转换,已知可能丢失信息的转换,以及跨类型域的转换,与值得缩小表示法相差。 以下转换分类为缩小转换:

布尔转换

  • BooleanByte、、SByteUShort、、ShortUIntegerIntegerULong、、Long、或DecimalSingleDouble

  • ByteSByteUShortShortUInteger、、IntegerLongULong、、DecimalSingleDoubleBoolean

数值转换

  • ByteSByte

  • SByteByteUShortUIntegerULong

  • UShortByteSByteShort

  • ShortByteSByteUShortUIntegerULong

  • UIntegerByteSByteUShortShortInteger

  • IntegerByteSByteUShortShortUIntegerULong

  • ULongByteSByteUShortShortUIntegerIntegerLong

  • LongByteSByteUShortShortUIntegerIntegerULong

  • DecimalByteSByteUShortShortUIntegerIntegerULongLong

  • SingleByte、、SByteUShortShortUIntegerInteger、或ULongLongDecimal

  • DoubleByteSByteUShort、、ShortUIntegerIntegerULong、或LongDecimalSingle

  • 从数值类型到枚举类型。

  • 从枚举类型到数值类型,其基础数值类型具有收缩转换。

  • 从枚举类型到另一个枚举类型。

引用转换

  • 从引用类型到更派生的类型。

  • 从类类型到接口类型,前提是类类型不实现接口类型或与其兼容的接口类型变体。

  • 从接口类型到类类型。

  • 从接口类型到另一个接口类型,前提是两种类型之间没有继承关系,并且前提是它们与变体不兼容。

匿名委托转换

  • 从为 lambda 方法重新分类而生成的匿名委托类型转换为任何较窄的委托类型。

数组转换

  • 从具有元素类型的Se数组类型S到具有元素类型的数组类型TTe,前提是以下所有内容均为 true:

    • S 并且 T 仅在元素类型中不同。
    • 两者 Se 都是 Te 引用类型,或者是未知为值类型的类型参数。
    • 从 <a0/> 到 存在收缩引用、数组或类型参数转换。
  • 从具有元素类型的Se数组类型S到具有枚举元素类型的数组类型TTe,前提是以下所有内容均为 true:

    • S 并且 T 仅在元素类型中不同。
    • Se 是基础类型的 Te ,或者它们都是共享相同基础类型的不同枚举类型。
  • 从具有枚举元素类型的排名 1 的数组类型/>,其中一个为 true:

    • 两者都是引用类型,或者是已知为引用类型的类型参数,并且存在从 <a0/> 到 的收缩引用、数组或类型参数转换
    • Se 是基础类型的 Te,或者它们都是共享相同基础类型的不同枚举类型。

值类型转换

  • 从引用类型到更派生的值类型。

  • 从接口类型到值类型,前提是值类型实现接口类型。

可以为 Null 的值类型转换

  • 从类型 T? 到类型 T

  • 从类型到类型T?S?,其中从类型到类型TS有收缩转换。

  • 从类型到类型TS?,其中从类型到类型TS有收缩转换。

  • 从类型转换为类型S?T,其中从类型S转换为类型T

字符串转换

  • StringChar

  • StringChar()

  • String/Boolean向/从StringBoolean

  • 和 、、UShortSByteUIntegerShortLongULongIntegerDecimalSingle之间的Double转换。ByteString

  • String/Date向/从StringDate

类型参数转换

  • Object 类型参数到类型参数。

  • 从类型参数到接口类型,前提是类型参数不受该接口约束或约束到实现该接口的类。

  • 从接口类型到类型参数。

  • 从类型参数到类约束的派生类型。

  • 从类型参数到类型参数 T 约束 Tx 的任何内容都有收缩转换。

类型参数转换

类型参数的转换由约束(如果有)确定。 类型参数 T 始终可以转换为自身、向/从 Object、向和传出任何接口类型。 请注意,如果类型T是运行时的值类型,则从T中转换到Object接口类型或接口类型将是装箱转换,从或从接口类型T转换Object将是取消装箱转换。 具有类约束 C 的类型参数定义从类型参数到 C 其基类的其他转换,反之亦然。 具有类型参数约束Tx的类型参数定义转换和Tx任何转换到的类型Tx参数T

一个数组,其元素类型是具有接口约束 I 的类型参数,其协变数组转换与元素类型为 I数组相同,前提是类型参数也具有 Class 或类约束(因为只有引用数组元素类型可以是协变)。 一个数组,其元素类型是具有类约束 C 的类型参数,其协变数组转换与元素类型为 C数组相同。

上述转换规则不允许从不受约束的类型参数转换为非接口类型,这可能令人吃惊。 这样做的原因是防止混淆此类转换的语义。 例如,请考虑以下声明:

Class X(Of T)
    Public Shared Function F(t As T) As Long 
        Return CLng(t)    ' Error, explicit conversion not permitted
    End Function
End Class

如果允许转换 TInteger ,人们很容易期望 X(Of Integer).F(7) 会返回 7L。 但是,它不会,因为只有在已知类型在编译时为数值时才考虑数值转换。 为了明确语义,必须改为编写上述示例:

Class X(Of T)
    Public Shared Function F(t As T) As Long
        Return CLng(CObj(t))    ' OK, conversions permitted
    End Function
End Class

User-Defined 转换

内部转换 是由语言(即此规范中列出的)定义的转换,而 用户定义的转换 是通过重载 CType 运算符定义的。 在类型之间进行转换时,如果没有适用的内部转换,则将考虑用户定义的转换。 如果有 特定于 源类型和目标类型的用户定义的转换,则将使用用户定义的转换。 否则,将生成编译时错误。 最具体的转换是其作数“最靠近”源类型的转换,其结果类型与目标类型“最接近”。 确定要使用的用户定义的转换时,将使用最具体的扩大转换;如果没有最具体的扩大转换,将使用最具体的缩小转换。 如果没有最具体的收缩转换,则转换未定义,并且会发生编译时错误。

以下部分介绍了如何确定最具体的转换。 它们使用以下术语:

如果从类型到类型AB存在内部扩大转换,并且既AB既不是接口,A包含并BB包含A

一组类型中最 包含 的类型是包含集中所有其他类型的一种类型。 如果没有单个类型包含所有其他类型,则集没有最包含的类型。 在直观的术语中,最包含的类型是集中的“最大”类型 -- 可通过扩大转换将其他类型转换为的一种类型。

一组类型中最 包含 的类型是集合中所有其他类型包含的一种类型。 如果没有其他所有类型包含单个类型,则集没有最包含的类型。 在直观的术语中,最包含的类型是集中的“最小”类型 -- 一种类型,可通过缩小转换转换为其他每种类型。

收集类型的 T?候选用户定义转换时,将改用用户定义的 T 转换运算符。 如果要转换为的类型也是可以为 null 的值类型,则会提升涉及非可为 null 值类型的任何 T用户定义的转换运算符。 转换运算符从T此提升为从T?转换到S?的转换,并T?T在必要时转换为用户定义转换运算符,然后根据需要转换为S?S该运算符STS 但是,如果要转换的值是Nothing,则提升的转换运算符将直接转换为类型化S?的值Nothing。 例如:

Structure S
    ...
End Structure

Structure T
    Public Shared Widening Operator CType(ByVal v As T) As S
        ...
    End Operator
End Structure

Module Test
    Sub Main()
        Dim x As T?
        Dim y As S?

        y = x                ' Legal: y is still null
        x = New T()
        y = x                ' Legal: Converts from T to S
    End Sub
End Module

解析转换时,用户定义转换运算符始终优先于提升的转换运算符。 例如:

Structure S
    ...
End Structure

Structure T
    Public Shared Widening Operator CType(ByVal v As T) As S
        ...
    End Operator

    Public Shared Widening Operator CType(ByVal v As T?) As S?
        ...
    End Operator
End Structure

Module Test
    Sub Main()
        Dim x As T?
        Dim y As S?

        y = x                ' Calls user-defined conversion, not lifted conversion
    End Sub
End Module

在运行时,评估用户定义的转换最多可能需要三个步骤:

  1. 首先,使用内部转换将值从源类型转换为作数类型(如有必要)。

  2. 然后,将调用用户定义的转换。

  3. 最后,用户定义的转换的结果使用内部转换转换为目标类型(如有必要)。

请务必注意,对用户定义的转换的评估绝不涉及多个用户定义的转换运算符。

最具体的扩大转换

使用以下步骤确定两种类型之间的最具体的用户定义的扩大转换运算符:

  1. 首先,收集所有候选转换运算符。 候选转换运算符是源类型中用户定义的扩大转换运算符和目标类型中所有用户定义的扩大转换运算符。

  2. 然后,从集中删除所有不适用的转换运算符。 如果源类型具有从源类型到作数类型的内部扩大转换运算符,并且从运算符结果到目标类型,则转换运算符适用于源类型和目标类型。 如果没有适用的转换运算符,则没有最具体的扩大转换。

  3. 然后,确定适用转换运算符的最特定源类型:

    • 如果任何转换运算符直接从源类型转换,则源类型是最具体的源类型。

    • 否则,最具体的源类型是转换运算符组合的源类型集中最包含的类型。 如果找不到最包含的类型,则没有最具体的扩大转换。

  4. 然后,确定适用转换运算符的最具体目标类型:

    • 如果任何转换运算符直接转换为目标类型,则目标类型是最具体的目标类型。

    • 否则,最具体的目标类型是转换运算符组合的目标类型集中最包含的类型。 如果找不到最包含的类型,则没有最具体的扩大转换。

  5. 然后,如果完全一个转换运算符从最具体的源类型转换为最具体的目标类型,则这是最具体的转换运算符。 如果存在多个这样的运算符,则不存在最具体的扩大转换。

最具体的缩小转换

使用以下步骤确定两种类型之间的最具体的用户定义的缩小转换运算符:

  1. 首先,收集所有候选转换运算符。 候选转换运算符是源类型中的所有用户定义的转换运算符和目标类型中的所有用户定义的转换运算符。

  2. 然后,从集中删除所有不适用的转换运算符。 如果源类型到作数类型存在内部转换运算符,并且从运算符的结果到目标类型,则转换运算符适用于源类型和目标类型。 如果没有适用的转换运算符,则没有最具体的收缩转换。

  3. 然后,确定适用转换运算符的最特定源类型:

    • 如果任何转换运算符直接从源类型转换,则源类型是最具体的源类型。

    • 否则,如果任何转换运算符从包含源类型的类型转换,则最具体的源类型是这些转换运算符的源类型的组合集中最包含的类型。 如果找不到最包含的类型,则没有最具体的缩小转换。

    • 否则,最具体的源类型是转换运算符组合的源类型集中最包含的类型。 如果找不到最包含的类型,则没有最具体的缩小转换。

  4. 然后,确定适用转换运算符的最具体目标类型:

    • 如果任何转换运算符直接转换为目标类型,则目标类型是最具体的目标类型。

    • 否则,如果任一转换运算符转换为目标类型包含的类型,则最具体的目标类型是这些转换运算符的源类型的组合集中最包含的类型。 如果找不到最包含的类型,则没有最具体的缩小转换。

    • 否则,最具体的目标类型是转换运算符组合的目标类型集中最包含的类型。 如果找不到最包含的类型,则没有最具体的缩小转换。

  5. 然后,如果完全一个转换运算符从最具体的源类型转换为最具体的目标类型,则这是最具体的转换运算符。 如果存在多个此类运算符,则不存在最具体的缩小转换。

本机转换

其中几个转换被分类为 本机转换 ,因为它们由 .NET Framework 本机支持。 这些转换是可以通过使用 DirectCastTryCast 转换运算符以及其他特殊行为进行优化的转换。 分类为本机转换的转换包括:标识转换、默认转换、引用转换、数组转换、值类型转换和类型参数转换。

主导类型

给定一组类型时,通常有必要在类型推理等情况下确定集的主要 类型 。 一组类型的主导类型由先删除一个或多个其他类型没有隐式转换到的任何类型来确定。 如果此时没有留下任何类型,则不存在主导类型。 然后,主要类型是其余类型中最包含的类型。 如果有多个类型最包含,则不存在主导类型。