【问题标题】:Does it make sense to use "as" instead of a cast even if there is no null check? [closed]即使没有空检查,使用“as”而不是强制转换是否有意义? [关闭]
【发布时间】:2011-01-09 12:49:38
【问题描述】:

在开发博客、在线代码示例和(最近)甚至是一本书中,我总是对这样的代码磕磕绊绊:

var y = x as T;
y.SomeMethod();

或者,更糟糕的是:

(x as T).SomeMethod();

这对我来说没有意义。如果您确定x 的类型为T,则应使用直接转换:(T)x。如果不确定,可以使用as,但需要在执行某些操作之前检查null。以上代码所做的就是将(有用的)InvalidCastException 变成(无用的)NullReferenceException

只有我认为这是对as 关键字的公然滥用吗?还是我错过了一些明显的东西,而上面的模式实际上是有道理的?

【问题讨论】:

  • 看到会更有趣(像 S 一样亲吻)。SteveIsSuchA();但我同意,这是一种滥用。
  • 这比写((T)x).SomeMethod()酷多了,不是吗? ;)(开个玩笑,你当然是对的!)
  • @P Daddy 我不同意,非常好的问题(这个代码模式真的有意义吗),并且非常有用。对问题 +1,对投票结束的人皱眉。
  • Lucerno 是对的,这种编码模式是通过避免使用括号而引起的。接触 Lisp 后无法治愈。
  • 优化代码:(f as T).SomeMethod() ;)

标签: c# casting type-conversion


【解决方案1】:

您的理解是正确的。这听起来像是试图对我进行微优化。当您确定类型时,您应该使用正常转换。除了产生更合理的异常之外,它也很快失败。如果您对类型的假设有误,您的程序将立即失败,您将能够立即看到失败的原因,而不是等待 NullReferenceExceptionArgumentNullException 甚至在某个时间出现逻辑错误未来。通常,as 表达式后面没有 null 检查某处是代码异味。

另一方面,如果您不确定转换并预计它会失败,您应该使用as 而不是用try-catch 块包裹的普通转换。此外,建议使用as,而不是类型检查,然后进行强制转换。而不是:

if (x is SomeType)
   ((SomeType)x).SomeMethod();

is关键字生成isinst instruction,为强制转换生成castclass instruction(有效地执行了两次强制转换),您应该使用:

var v = x as SomeType;
if (v != null)
    v.SomeMethod();

这只会生成isinst 指令。前一种方法在多线程应用程序中存在潜在缺陷,因为竞争条件可能会导致变量在is 检查成功后更改其类型并在强制转换行失败。后一种方法不容易出现这个错误。


不推荐在生产代码中使用以下解决方案。如果你真的讨厌 C# 中的这种基本结构,你可以考虑改用 VB 或其他语言。

如果一个人非常讨厌强制转换语法,他/她可以编写一个扩展方法来模仿强制转换:

public static T To<T>(this object o) { // Name it as you like: As, Cast, To, ...
    return (T)o;
}

并使用简洁的[?] 语法:

obj.To<SomeType>().SomeMethod()

【讨论】:

  • 我认为比赛条件无关紧要。如果您遇到此问题,那么您的代码不是线程安全的,有比使用关键字“as”更可靠的方法来解决它。 +1 其余答案。
  • @RMorrisey:我至少想到了一个例子:假设您有一个cache 对象,另一个线程试图通过将其设置为null 来使其无效。在无锁场景中,可能会出现这种情况。
  • is+cast 足以触发来自 FxCop 的“不要进行不必要的强制转换”警告:msdn.microsoft.com/en-us/library/ms182271.aspx 这应该是避免构造的足够理由。
  • 你应该避免在Object上做扩展方法。在值类型上使用该方法会导致它被不必要地装箱。
  • @MgSam 显然,这样的用例对于这里的To 方法没有意义,因为它只在继承层次结构中进行转换,对于值类型而言,无论如何都涉及装箱。当然,整个想法更多的是理论而不是严肃。
【解决方案2】:

恕我直言,asnull 检查结合使用才有意义:

var y = x as T;
if (y != null)
    y.SomeMethod();

【讨论】:

    【解决方案3】:

    使用“as”不会应用用户定义的转换,而演员会在适当的时候使用它们。在某些情况下,这可能是一个重要的区别。

    【讨论】:

    【解决方案4】:

    我在这里写了一点:

    http://blogs.msdn.com/ericlippert/archive/2009/10/08/what-s-the-difference-between-as-and-cast-operators.aspx

    我明白你的意思。我同意它的主旨:演员表操作员传达“我确信这个对象可以转换为那种类型,如果我错了,我愿意冒异常的风险”,而“as”操作员传达“我不确定这个对象是否可以转换为那种类型;如果我错了,给我一个空值”。

    但是,有一个细微的差别。 (x as T).Whatever() 传达“我不仅知道 x 可以转换为 T,而且,这样做只涉及引用或拆箱转换,而且 x 不为空”。这确实传达了与 ((T)x).Whatever() 不同的信息,也许这就是代码作者的意图。

    【讨论】:

    • 我不同意你在最后一句话中对代码作者的推测性辩护。 ((T)x).Whatever() also 表示x 不是 [意图] null,我高度怀疑作者通常会关心转换为 T 是否仅发生引用或取消装箱转换,或者如果它需要用户定义的或表示更改的转换。毕竟,如果我定义public static explicit operator Foo(Bar b){},那么显然我的意图是Bar 被认为与Foo 兼容。我很少想避免这种转换。
    • 好吧,也许大多数代码作者不会做出这种细微的区别。我个人可能是,但如果我是,我会为此添加评论。
    【解决方案5】:

    我经常看到对this misleading article 的引用作为“as”比强制转换更快的证据。

    本文更明显的误导性方面之一是图形,它没有表明正在测量的内容:我怀疑它正在测量 失败 演员表(其中“as”显然比 no 要快得多抛出异常)。

    如果您花时间进行测量,那么您会发现,如您所料,当转换成功时,转换比“as”

    我怀疑这可能是“cargo cult”使用 as 关键字而不是 cast 的原因之一。

    【讨论】:

    • 感谢您的链接,这很有趣。根据我对文章的理解,他确实比较了非异常情况。尽管如此,这篇文章是为 .net 1.1 写的,cmets 指出这在 .net 2.0 中发生了变化:性能现在几乎相等,前缀转换甚至稍微快了一点。
    • 这篇文章确实暗示他在比较非异常情况,但我很久以前做过一些测试,即使使用 .NET 1.x 也无法重现他声称的结果。而且由于本文没有提供用于运行基准测试的代码,因此无法说出所比较的内容。
    • “货物崇拜” - 完美。查看“Cargo Cult Science Richard Feynman”了解完整信息。
    【解决方案6】:

    直接转换需要一对括号而不是as 关键字。因此,即使您 100 % 确定类型是什么,它也可以减少视觉混乱。

    同意例外的事情,不过。但至少对我来说,as 的大多数使用归结为事后检查 null,我发现这比捕获异常更好。

    【讨论】:

      【解决方案7】:

      当我使用“as”时,99% 的情况是我不确定实际的对象类型是什么

      var x = obj as T;
      if(x != null){
       //x was type T!
      }
      

      我不想捕获显式转换异常,也不想使用“is”进行两次转换:

      //I don't like this
      if(obj is T){
        var x = (T)obj; 
      }
      

      【讨论】:

      • 您刚刚描述了as 的正确用例。剩下的 1% 是多少?
      • 打错字了? =) 我的意思是 99% 的时间我都使用这个 exact 代码 sn-p,而有时我可能会在方法调用或其他地方使用“as”。
      • D'oh,这怎么不如第二个流行的答案有用???
      • +1 我同意说出来和鲁本斯·法里亚斯的回答一样有价值——希望人们会来到这里,这将是一个有用的例子
      【解决方案8】:

      只是因为人们喜欢它的外观,它的可读性很强。

      让我们面对现实吧:类 C 语言中的强制转换/转换运算符非常糟糕,可读性很差。如果 C# 采用以下 Javascript 语法,我会更好:

      object o = 1;
      int i = int(o);
      

      或者定义一个to 操作符,相当于as

      object o = 1;
      int i = o to int;
      

      【讨论】:

      • 您知道,您提到的 JavaScript 语法在 C++ 中也是允许的。
      • @PDaddy:虽然它不是直接 100% 兼容的替代语法,也不是那样的(运算符 X 与转换构造函数)
      • 我希望它使用dynamic_cast&lt;&gt;()(和类似的)的C++语法。你正在做一些丑陋的事情,它应该看起来很丑陋。
      【解决方案9】:

      人们非常喜欢as,因为这让他们感到不受例外的影响……就像盒子上的保证一样。一个人在盒子上放了一个花哨的保证,因为他希望你在里面感到温暖和温暖。你想你晚上把那个小盒子放在枕头下,保证仙女可能会下来留下四分之一,我说得对吗泰德?

      回到主题...当使用直接强制转换时,可能出现无效强制转换异常。所以人们将as 用作满足他们所有选角需求的一揽子解决方案,因为as(本身)永远不会抛出异常。但有趣的是,在您给(x as T).SomeMethod(); 的示例中,您正在用无效转换异常换取空引用异常。当您看到异常时,这会混淆真正的问题。

      我一般不会过多使用as。我更喜欢 is 测试,因为对我来说,它看起来更具可读性并且比尝试强制转换和检查 null 更有意义。

      【讨论】:

      • "我更喜欢 is 测试" - "is" 后跟强制转换当然比 "as" 后跟 null 测试慢(就像 "IDictionary.ContainsKey" 后跟使用索引器比“IDictionary.TryGetValue”慢)。但是,如果您发现它更具可读性,那么毫无疑问,差异并不显着。
      • 中间部分的重要陈述是人们如何将as 用作一揽子解决方案,因为它让他们感到安全。
      【解决方案10】:

      这必须是我的top peeves 之一。

      Stroustrup 的 D&E 和/或我现在找不到的一些博客文章讨论了 to 运算符的概念,该运算符将解决 https://stackoverflow.com/users/73070/johannes-rossel 提出的观点(即,与 as 相同的语法,但具有 DirectCast 语义)。

      这没有得到实现的原因是因为演员应该会导致痛苦和丑陋,所以你会被推开使用它。

      遗憾的是,“聪明”的程序员(通常是书籍作者 (Juval Lowy IIRC))通过以这种方式滥用 as 来绕过这个问题(C++ 不提供 as,可能是因为这个原因)。

      即使是 VB,在统一语法方面也更加一致,迫使您选择 TryCastDirectCast下定决心

      【讨论】:

      • +1。您可能指的是DirectCast 行为,而不是语法
      • @Heinzi:Ta 表示 +1。好点子。决定做个聪明人,改用semantics:P
      • 鉴于 C# 并没有假装与 C、C++ 或 Java 兼容,我发现自己对它从这些语言中借用的一些东西感到愤怒。它超越了“我知道这是一个 X”和“我知道这不是一个 X,但可以表示为一个”,到“我知道这不是一个 X,并且可能无法真正表示为一个” ,但无论如何给我一个X。”我可以看到double-to-int 转换的用处,如果double 不代表可以适合Int32 的确切值,那么double-to-int 转换将失败,但是(int)-1.5 产生-1 简直丑陋。
      • @supercat 是的,但是众所周知,语言设计并不容易——看看 C# 可空对象中涉及的一系列权衡。唯一已知的解药是定期阅读 C# 的深度版本 :) 谢天谢地,这些天我更关心理解 F# 的细微差别,并且在很多这些问题上都更加理智。
      • @RubenBartelink:我不太清楚可空类型应该解决哪些具体问题,但我认为在大多数情况下,拥有两个公共字段的 MaybeValid&lt;T&gt; 会更好IsValidValue 可以根据需要使用哪些代码。那将允许例如MaybeValid&lt;TValue&gt; TryGetValue(TKey key) { var ret = default(MaybeValid&lt;TValue&gt;); ret.IsValid = dict.TryGetValue(key, out ret.Value); return ret; }。与Nullable&lt;T&gt; 相比,这不仅可以节省至少两次复制操作,而且any 类型T 也值得——不仅仅是类。
      【解决方案11】:

      我相信 as 关键字可以被认为是外观更优雅的版本 dynamic_cast 来自 C++。

      【讨论】:

      • 我想说 C# 中的直接转换更像是 C++ 中的 dynamic_cast
      • 我认为 C# 中的直接转换更等同于 C++ 中的 static_cast。
      • @Ruben Bartelink:它只返回带有指针的空值。对于应尽可能使用的引用,它会抛出 std::bad_cast
      • @Andrew Garrison:static_cast 不执行运行时类型检查。在 C# 中没有与此类似的强制转换。
      • 遗憾的是,我不知道你甚至可以在引用上使用强制转换,因为我只在指针上使用过它们,但是 P Daddy 绝对正确!
      【解决方案12】:

      它可能更受欢迎,没有技术原因,只是因为它更容易阅读和更直观。 (并不是说只是尝试回答问题会更好)

      【讨论】:

        【解决方案13】:

        使用“as”的一个原因:

        T t = obj as T;
         //some other thread changes obj to another type...
        if (t != null) action(t); //still works
        

        而不是(错误代码):

        if (obj is T)
        {
             //bang, some other thread changes obj to another type...
             action((T)obj); //InvalidCastException
        }
        

        【讨论】:

        • 如果你有这么丑陋的比赛条件,你就有更大的问题(但同意这是一个很好的样本,所以+1
        • -1 因为这使谬误永久化。如果其他线程可以更改 obj 的类型,那么您仍然有问题。声称“//仍然有效”是非常不可能成立的,因为 t 将用作指向 T 的指针,但它指向不再是 T 的内存。当另一个线程更改类型时,这两种解决方案都不起作用obj,而 action(t) 正在进行中。
        • @Stephen C. Steel:你似乎很困惑。更改obj 的类型意味着更改obj 变量本身以保存对另一个对象的引用。它不会改变最初由obj 引用的对象所在的内存内容。这个原始对象将保持不变,t 变量仍将持有对它的引用。
        • @P Daddy - 我认为你是对的,但我错了:如果 obj 从 T 对象反弹到 T2 对象,那么 t 仍将指向旧的 T 对象。由于 t 仍然引用旧对象,它不能被垃圾回收,因此旧 T 对象将保持有效。我的竞争条件检测器电路是在 C++ 上训练的,其中使用 dynamic_cast 的类似代码将是一个潜在的问题。
        猜你喜欢
        • 2010-11-03
        • 2011-01-17
        • 2015-06-03
        • 1970-01-01
        • 1970-01-01
        • 2017-12-06
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多