使用“;as”有意义吗?而不是一个强制转换,即使没有空检查?

在开发博客、在线代码示例和(最近)甚至是一本书中,我经常遇到这样的代码:

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

或者,更糟糕的是:

(x as T).SomeMethod();

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

我是唯一一个谁认为这是一个公然滥用as关键字?还是我错过了一些明显的东西,而上面的模式实际上是有意义的?

40228 次浏览

你的理解是正确的。对我来说,这听起来像是在尝试微优化。当确定类型时,应该使用正常类型强制转换。除了生成一个更合理的异常之外,它还会快速失败。如果你对类型的假设是错误的,你的程序将立即失败,你将能够立即看到失败的原因,而不是等待NullReferenceExceptionArgumentNullException,甚至是将来某个时候的逻辑错误。一般来说,as表达式后面没有null检查的地方是代码气味。

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

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

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

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()

恕我直言,当与null检查结合在一起时,as才有意义:

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

直接强制转换比as关键字更需要一对括号。所以即使在你100%确定是什么类型的情况下,它也减少了视觉上的混乱。

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

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

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关键字可以被认为是一个看起来更优雅的版本 dynamic_cast from c++ .

只是因为人们喜欢它的样子,所以它很有可读性。

让我们面对现实吧:类c语言中的强制转换操作符在可读性方面相当糟糕。我希望它更好,如果c#采用Javascript语法:

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

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

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

它之所以更受欢迎,可能不是因为技术原因,而是因为它更容易阅读,更直观。(不是说这样会更好,只是试着回答问题)

人们如此喜欢__abc0,因为它让他们在异常面前感到安全……就像盒子上的担保。一个男人在盒子上放了一个华丽的保证,因为他想让你觉得里面温暖舒适。你觉得晚上把那个小盒子放在枕头下面,担保仙女可能会下来留下25美分,对吗,泰德?

回到正题…当使用直接强制转换时,无效强制转换异常存在可能性。因此,人们应用as作为所有强制转换需求的通用解决方案,因为as(本身)永远不会抛出异常。但有趣的是,在你给出(x as T).SomeMethod();的例子中,你正在用一个无效的强制转换异常换取一个空引用异常。当您看到异常时,会混淆真正的问题。

我通常不太使用as。我更喜欢is测试,因为对我来说,它看起来更可读,比尝试强制转换和检查null更有意义。

我经常看到这篇误导性的文章的引用作为“as”比强制转换更快的证据。

本文中一个比较明显的误导性方面是图形,它没有指出正在测量什么:我怀疑它正在测量失败的类型转换(其中“as”显然要快得多,因为没有抛出异常)。

如果你花时间做测量,那么你会看到,当强制转换成功时,正如你所期望的,比“as”。

我怀疑这可能是“货物崇拜”使用as关键字而不是演员的原因之一。

使用“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
}

这必须是最讨厌中的一个。

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

这个没有被实现的原因是因为施法应该会造成疼痛和丑陋,所以你会被推远使用它。

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

甚至VB也有统一的语法,强迫你选择TryCastDirectCast下定决心吧!

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

我在这里写了一点:

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

我明白你的意思。我同意它的重点:强制转换操作符传达的是“我确信这个对象可以转换为那种类型,如果我错了,我愿意冒一个异常的风险”,而“as”操作符传达的是“我不确定这个对象可以转换为那种类型;如果我错了,就给我一个null。”

然而,有一个微妙的区别。whatever()表示“我知道x不仅可以转换为T,而且这样做只涉及引用或开箱转换,而且x不是空的”。这确实传达了不同于(T)x). whatever()的信息,也许这就是代码作者的意图。