关于c#:即使没有空检查,使用“as”而不是强制转换是否有意义?

Does it make sense to use “as” instead of a cast even if there is no null check?

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

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

或者,更糟的是:

1
(x as T).SomeMethod();

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

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


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

另一方面,如果您不确定铸型并希望它失败,则应使用as,而不是用try-catch块包裹的普通铸型。此外,推荐使用as而不是类型检查,然后是强制转换。而不是:

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

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

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

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

不建议在生产代码中使用以下解决方案。如果您真的讨厌C语言中的这种基本结构,您可以考虑切换到VB或其他语言。

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

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

用干净的语法:

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


imho,asnull检查结合时才有意义:

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


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


我在这里写了一点:

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

我理解你的观点。我同意它的观点:一个cast操作符传递"我确信这个对象可以转换成那个类型,如果我错了,我愿意冒异常风险",而"as"操作符传递"我不确定这个对象是否可以转换成那个类型;如果我错了,给我一个空值"。

然而,有一个微妙的区别。(x为t),whatever()传递"我不仅知道x可以转换为t,而且这样做只涉及引用或取消绑定转换,而且x不是空的"。它传递的信息与((t)x).whatever()不同,也许这就是代码作者的意图。


我经常看到引用这篇误导性文章作为证据,证明"as"比"casting"更快。

这篇文章中一个更明显的误导性方面是图形,它没有指示正在测量的内容:我怀疑它测量的是失败的强制转换(其中"as"明显更快,因为没有异常被抛出)。

如果你花时间进行测量,那么你会发现,正如你所期望的那样,施法比施法成功时的"as"快。

我怀疑这可能是"货物崇拜"使用as关键字而不是cast的一个原因。


直接强制转换需要一对括号,而不是as关键字。因此,即使在100%确定类型的情况下,它也可以减少视觉混乱。

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


这只是因为人们喜欢它的外观,它是非常可读的。

让我们面对现实吧:C语言中的转换/转换操作符非常糟糕,可读性很强。如果C采用以下任意一种JavaScript语法,我希望它更好:

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

或定义一个to运算符,即as的铸造等价物:

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


当我使用"as"时,99%的时间是当我不确定实际的对象类型时

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

我不想捕捉显式的强制转换异常,也不想进行两次强制转换,使用"is":

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


这一定是我最大的毛病之一。

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

之所以没有实现这一点,是因为强制转换会导致疼痛和难看,所以您会被推离使用它。

遗憾的是,"聪明的"程序员(通常是书作者(Juval Lowy Irc))通过滥用EDCOX1第1条来绕过这个问题(C++不提供EDOCX1×1),可能是因为这个原因。

即使是vb在统一的语法上也有更大的一致性,它迫使您选择TryCastDirectCast,并做出决定!


人们如此喜欢as是因为它让他们觉得不受例外的影响…就像盒子上的保证。一个家伙在盒子上放了一个精美的保证,因为他想让你感觉到里面的温暖和烘烤。你觉得你晚上把那个小盒子放在枕头下面,担保仙女可能会下来留下一角五分之一,对吗,泰德?

回到主题…使用直接强制转换时,可能存在无效强制转换异常。因此,人们将as作为一个整体解决方案来满足他们所有的铸造需求,因为as本身不会抛出一个例外。但有趣的是,在您给(x as T).SomeMethod();的示例中,您将一个无效的强制转换异常与一个空引用异常进行交易。当您看到异常时,它会混淆真正的问题。

我一般不会用太多的东西。我更喜欢is测试,因为在我看来,它比尝试强制转换和检查空值更具可读性和意义。


我相信,as关键字可以被认为是EDCOX1,18来自C++。


使用"as"的一个原因:

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

代替(错误代码):

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


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