【问题标题】:Why Cast (CType/DirectCast) controls versus implicit conversion为什么 Cast (CType/DirectCast) 控件与隐式转换
【发布时间】:2013-12-30 16:52:10
【问题描述】:

假设我在 webforms GridViewRow 中有一个控件...

<asp:Literal ID="ltl_abc" runat="server" />

在 RowDataBound 事件中,我可以使用以下任何方法访问控件。我一直使用 DirectCast:

Protected Sub gv_RowDataBound(sender As Object, e As GridViewRowEventArgs) Handles gv.RowDataBound
    Select Case e.Row.RowType
        Case DataControlRowType.DataRow
            ' 1) Dim ltl_abc As Literal = DirectCast(e.Row.FindControl("ltl_abc"), Literal)
            ' 2) Dim ltl_abc As Literal = CType(e.Row.FindControl("ltl_abc"), Literal)
            ' 3) Dim ltl_abc As Literal = e.Row.FindControl("ltl_abc")

使用任何特定方法有什么好处吗?我猜 DirectCast 的效率稍微高一些,但可能容易出错,但是隐式转换(选项 3)有什么危险吗?

从历史上看,在我尝试为控件的属性实际赋值之前,我从未见过任何错误,这让我认为第一步并不那么重要?

请注意,这不是 DirectCast 与 CType 的讨论,更多关于这里是否需要强制转换?

更新清楚

Protected Sub gv_RowDataBound(sender As Object, e As GridViewRowEventArgs) Handles gv.RowDataBound
    Select Case e.Row.RowType
        Case DataControlRowType.DataRow

            ' This works fine, but no explicit casting is done:
            Dim ltl_abc As Literal = e.Row.FindControl("ltl_abc") ' no (explicit) cast
            ltl_abc.Text = "Hello World"

            ' This also works until I try to access the object's properties
            Dim ltl_abc As Literal = DirectCast(e.Row.FindControl("NonExistentId"), Literal)

为什么开发人员应该强制转换(在这个例子中),或者这个例子太简单了?

【问题讨论】:

  • 3) 是否与Option Strict 一起使用? (这里不是 VB.NET 人员,但我认为这是推荐使用的东西。)因为如果不是,那么这个问题就简化为“我应该使用 Option Strict 吗?”,这对我来说是个问题。跨度>
  • 我使用了 Explicit,根据 MSDN,这意味着 Strict,是的,它有效。但我认为这是因为变量被声明为 as Literal
  • 即使控件不存在也可以工作(除非您尝试为属性分配值,否则不会抛出空引用),Dim fakeCtrl as Literal = e.Row.FindControl("NotThere")
  • 你倒退了。 Option Strict On 暗示 Option Explicit On,而不是相反。

标签: asp.net vb.net asp.net-4.5


【解决方案1】:

对于您的情况,TryCast 带有IsNot Nothing 的检查可能更有益。

不过,要了解何时以及为何使用哪个,请先查看 MSDN 对它们的定义。

DirectCast

引入基于继承或实现的类型转换操作。 ... DirectCast 不使用 Visual Basic 运行时帮助程序进行转换...

CType

返回将表达式显式转换为指定数据类型、对象、结构、类或接口的结果。

Implicit Conversion

隐式转换不需要源代码中的任何特殊语法。 ...显式转换使用类型转换关键字

TryCast

引入了不抛出异常的类型转换操作。 ... TryCast 返回 Nothing (Visual Basic),因此您不必处理可能的异常,只需针对 Nothing 测试返回的结果。

脱离这些定义,我们可以假设CType 将根据给定的System.Type 进行外部调用,而DirectCast 将仅使用不同名字下的现有对象。同时,通过隐式转换,VB 只会尝试执行代码。然而,TryCast 将尝试强制转换对象或仅返回 Nothing(想想 C# 中的 as 运算符)

例如:

' works
Dim obj As Object = "I'm a string!" 'obj.GetType() -> System.String
Dim s = DirectCast(obj, String)

' throws error: Unable to cast object of type 'System.Int32' to type 'System.String'.
Dim obj As Object = 42 'obj.GetType() -> System.Int32
Dim s = DirectCast(obj, String)

第一个示例有效,因为obj 已经是刚刚定义为ObjectString。没有发生实际的转换。

现在让我们看看CType

' works
Dim obj As Object = "I'm a string!" 'obj.GetType() -> System.String
Dim s = CType(obj, String)

' works - would prefer to use CStr() here instead, since it's more explicit (see below)
Dim obj As Object = 42 'obj.GetType() -> System.Int32
Dim s = CType(obj, String)

最后,隐式转换:

' works with Option Explicit. Throws build error with Option Strict: Option Strict On disallows implicit conversions from 'Object' to 'String'.
Dim obj As Object = "I'm a string!"    'obj.GetType() -> System.String
Dim s As String = obj

' same as above
Dim obj As Object = 42 'obj.GetType() -> System.Int32
Dim s As String = obj

这两种方法都有效,但请记住,VB.NET 在这里调用了一个单独的库来完成这项繁琐的工作:

DirectCast:

IL_0000: nop
IL_0001: ldstr "I'm a string!"
IL_0006: stloc.0
IL_0007: ldloc.0
IL_0008: castclass [mscorlib]System.String
IL_000d: stloc.1
IL_000e: nop
IL_000f: ret

CType/隐式转换(编译相同):

IL_0000: nop
IL_0001: ldstr "I'm a string!"
IL_0006: stloc.0
IL_0007: ldloc.0
IL_0008: call string [Microsoft.VisualBasic]Microsoft.VisualBasic.CompilerServices.Conversions::ToString(object)
IL_000d: stloc.1
IL_000e: nop
IL_000f: ret

因此,基本上,由于 .NET 需要调用外部方法来确定它需要做什么来转换对象,CType/implicit 的运行速度会稍慢(example benchmarks and examples here)。请注意,由于它们在 MSIL 中编译相同,CType 和隐式转换应该执行相同。

那么你什么时候使用它们呢?我通常遵循几个简单的规则

  1. 如果我知道(或期望)我的对象已经是我的目标类型,只是定义不同,我使用DirectCast
  2. 如果我的对象与我的目标类型不同,我使用适当的转换方法。示例:Dim myInt = CInt("42")。注意,它的编译方式与 IL 中的CType 相同
  3. 如果我不确定传入的类型,我使用TryCast
  4. 如果我使用泛型进行强制转换/转换,我将使用 DirectCast 和/或 Convert.ChangeType,具体取决于上下文

您也可以使用CType 作为第二个,但在我看来,如果我知道我正在转换为Integer,那么我会选择更明确的CInt。但是,如果您启用了Option Strict,那么如果您将错误的内容传递给任一方式,您都应该会收到构建错误。

此外,虽然您可能想尝试用 TryCast 代替 DirectCast,但请查看有关主要差异和用途的 SO 问题的答案:Why use TryCast instead of Directcast?

如果您注意到,我没有在其中包含隐式输入。为什么?好吧,主要是因为我使用Option Strict On 进行编码,并且在缩小类型时它实际上不允许隐式转换(请参阅"Widening and Narrowing Conversions")。否则,就 .NET 而言,它与 CType 几乎相同

好的,现在所有这些都完成了,让我们看看所有三个(我猜是四个)Control 对象:

' control is just defined as a regular control
Dim control As New Control    

' Runtime Error: Unable to cast object of type 'System.Web.UI.Control' to type 'System.Web.UI.LiteralControl'
Dim literal_1 As LiteralControl = DirectCast(control, LiteralControl)

' Runtime Error: Unable to cast object of type 'System.Web.UI.Control' to type 'System.Web.UI.LiteralControl'
Dim literal_2 As LiteralControl = CType(control, LiteralControl)

' returns literal_3 -> Nothing
Dim literal_3 As LiteralControl = TryCast(control, LiteralControl)

还有一个:

' control as a LiteralControl stored as a Control
Dim control As Control = New LiteralControl 

' works
Dim literal_1 As LiteralControl = DirectCast(control, LiteralControl)

' works
Dim literal_2 As LiteralControl = CType(control, LiteralControl)

' works
Dim literal_3 As LiteralControl = TryCast(control, LiteralControl)

因此,对于您的情况,看起来TryCast 带有IsNot Nothing 检查是要走的路。

【讨论】:

  • 这是一个关于 CType、TryCast 和 DirectCast 的非常好的讨论;但是 OP 明确表示他们也想涵盖隐式转换。您可能想要添加一些关于隐式转换行为以及何时以及为什么更喜欢您已经介绍过的任何内容的内容。
  • @EvilDr,我已经更新了答案以包含有关隐式转换的信息以及使用所有 Web 控件的示例。
  • @EvilDr,如果您的 e.Row.FindControl("NonExistentId")Literal,那么您的 DirectCast 将 100% 工作。但是,如果不是,那么它将引发运行时错误(参见上面的最后一个示例)。但是,只要它们都继承 Control,您就不会遇到构建错误,因为 .NET 在运行时会对其进行排序。如果您知道您的行控件将始终为Literal,那么您可以使用DriectCastTryCast 结合 If literal IsNotNothing 支票只是提供了一点保险。
  • 如果你的对象总是文字,那么隐式转换(你的数字 3)就可以了。
  • 另外,这不仅适用于反射。例如,如果您有一个具有Person 属性的User 类,但该人实际上是Person 子类Employee 的对象,那么如果您想使用@,则必须强制转换User.Person 987654380@ 具体方法/属性。如果没有演员表,它可以像普通的Person 一样正常工作。上面的帖子旨在为您提供足够的信息和示例,以便您了解何时以及为何需要使用特定类型的转换。
【解决方案2】:

要回答您关于是否需要强制转换的问题,“视情况而定”。

在您给出的示例中,这是必要的,因为您将变量声明为Literal 类型。

FindControl 方法返回一个ControlLiteral 继承自该Literal。因此,假设您不需要访问特定于 @987654328 的任何属性或方法,您可以想象将 ltl_abc 声明为 Control(从而避免将其强制转换为 Literal) @。

【讨论】:

  • 谢谢道格拉斯。你说[casting] is necessary because you are declaring your variables as type Literal,例如3),它在没有执行转换时工作Dim ltl_abc As Literal = e.Row.FindControl("ltl_abc")(我假设这是在后台隐式完成的)。我知道如果声明为Control 但随后需要使用Literal 控件的属性/方法,我必须转换为Literal,但在3 中实际发生了什么?
  • 在示例 #3 中,VB 正在执行从 ControlLiteral 的隐式转换。此外,如果转换失败(例如,如果您在 TextBox 而不是 Literal 上执行它),那么它将抛出 InvalidCastException
【解决方案3】:

DirectCast() 是最有效和最快速的,但是当目标不是您要查找的类型或 null 时会抛出异常。

TryCast()(您没有列出)与DirectCast() 几乎相同,但从不抛出异常,而是返回 null。

CType() 效率较低,只有在目标对象和目标对象类型之间没有可用的转换时才会抛出异常。如果存在转换,则它将运行该转换,如果目标为空,则该转换可能会失败。

Explicit 不会自动为你执行转换,如果类型不完全匹配则不会抛出异常(IE,将数组分配给 IEnumerable 变量),如果目标为 null 则不会抛出异常.因此,稍后尝试访问该对象时,您可能会遇到异常。所以它非常接近TryCast(),没有将对象分配给兼容对象的问题。

对于您的示例,我建议 DirectCast() 进行一些异常处理。

【讨论】:

  • DirectCast() ... will throw an exception when the target is... null.。在我尝试实际访问对象的属性之前,如果我执行 Dim lbl As Label = DirectCast(e.Row.FindControl("NonExistentControlId"), Label),它似乎不会正常工作。
【解决方案4】:

您可能想在这里查看答案:

https://stackoverflow.com/a/3056582/117215

您的问题归结为 DirectCast 与 CType。

至于为什么你不应该使用隐式转换,你已经在你的问题中指出了一些缺点(即如果没有找到就可以工作等)。

【讨论】:

  • 这是一个有用的链接,但与他的问题无关。他说:“请注意,这不是 DirectCast 与 CType 的讨论,更多关于这里是否需要强制转换?”
猜你喜欢
  • 2011-02-11
  • 2010-12-06
  • 2011-07-29
  • 2013-01-09
  • 2011-03-04
  • 2013-10-05
  • 1970-01-01
  • 2016-05-06
相关资源
最近更新 更多