在开发博客、在线代码示例(最近)甚至是一本书中,我经常遇到这样的代码:
1 2
| var y = x as T;
y.SomeMethod(); |
或者,更糟的是:
这对我来说没有意义。如果您确定x是T类型,则应使用直接强制转换:(T)x。如果您不确定,可以使用as,但在执行某些操作之前需要检查null。以上代码所做的就是将(有用的)InvalidCastException变成(无用的)NullReferenceException。
我是唯一一个认为这是公然滥用as关键字的人吗?或者我错过了一些明显的东西,而上面的模式实际上是有意义的?
- 看到(像S一样接吻)会更有趣。史蒂文issucha();但我同意,这是一种虐待。
- 这比写((T)x).SomeMethod()要酷得多,不是吗?(开玩笑,你当然是对的!)
- 我记得,通过"as"的强制转换比直接强制转换快——不管怎样,如果没有空检查,它是无用的,正如您所说的。
- 你说得对,我也很担心,但这其实更像是一个咆哮而不是一个问题,这不是这个网站的设计目的。投票结束。
- @贝奥武夫:根据我的测试,如果失败的话,as只比直铸快,这是可以预料的。即使速度更快,它仍然是错误的。
- @爸爸:都是。因为我主要是一个vb.net开发人员,所以这个问题("或者我错过了一些明显的东西,而上面的模式实际上是有意义的吗?")不是修辞。
- @爸爸,我不同意,这个问题很好(这个代码模式真的有意义吗),非常有用。+1对这个问题和任何投票结束的人皱眉。
- Lucerno是对的,这种编码模式是由试图避免使用括号引起的。暴露于口齿不清后无法治愈。
- 优化代码:(f as T).SomeMethod();
- 我只是好奇,它是否记录了在任何地方"作为"在兜帽下只是扔和埋葬一个无效的社会例外?只是好奇你是怎么知道的(不跟你争论,只是好奇…)
- @B没有。我的意思是:如果x不是t类型,((T)x).SomeMethod()将抛出一个invalidcastexception,这比(x as T).SomeMethod()引起的nullreferenceexception更有用。在后一种情况下,nullreferenceexception不是由as引起的,而是由(尝试的)方法调用引起的。
- @海因茨和马基:好吧,作为记录,我不仅投票赞成这个问题,而且投票赞成结束。
- 其他有趣的关于施法和约翰·斯基特的事实如下:stackoverflow.com/questions/496096/…
- "as"允许您强制转换,然后测试是否为空。静态强制转换需要类型测试,然后强制转换。因为直列式的"as"在我看来更清晰,因此很受欢迎。嵌套的括号总是很难读取。
- T a = (T)x为左铸造,(x as T).SomeMethod();为右铸造。我对此很满意,而且代码从太多的括号中清除了。btw if (x is T) (x as T).SomeMethod();代码中的公共行,因为我可以复制if条件(带括号),将i更改为a并将其用于方法调用。
- 如果某个方法是一个能明智地处理参数为空的扩展方法,并且如果另一个扩展方法具有签名SomeMethod(this U u),其中U是变量x的类型,则存在写入(x as T).SomeMethod()的一个有效但远程的原因。在这种情况下,代码编写器可能打算使用编译器通常不会选择的扩展方法。当然,演员也可以。但在这种特殊的情况下,我也可以使用as。
- @卢塞罗——我认真地看待你的笑话——我真的认为使用(x as T).SomeMethod()来避免那些额外的括号更易读,而且它只是传达我知道x不像埃里克·利珀特在这篇文章上写的那样——那么为什么这是如此错误?
你的理解是真的。对我来说,这听起来像是在尝试微观优化。当您确定类型时,应该使用普通的强制转换。除了生成更合理的异常之外,它也很快失败。如果您对类型的假设是错误的,那么您的程序将立即失败,您将能够立即看到失败的原因,而不是在将来某个时候等待NullReferenceException或ArgumentNullException,甚至逻辑错误。一般来说,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() |
- 我认为种族状况无关紧要。如果您遇到这个问题,那么您的代码就不是线程安全的,并且有比使用关键字"as"更可靠的方法来解决它。+其余答案为1。
- +1用于@rmorrisey。如果上面的代码有竞争条件,那么您几乎肯定会遇到严重的问题。否则,"编译语言"中的一对二字节码可能不会成为问题!
- @rmorrisey:我至少有一个例子:假设您有一个cache对象,另一个线程试图通过将其设置为null使其无效。在无锁场景中,可能会出现这种情况。
- is+cast足以触发fxcop:msdn.microsoft.com/en-us/library/ms182271.aspx发出的"不要不必要地强制转换"警告,这应该是避免构造的足够理由。
- 要点。我可以看到在那种情况下它是如何改进的。在我的代码中,我没有必要不使用锁就工作。
- 您应该避免在Object上进行扩展方法。对值类型使用该方法将导致不必要地对其进行装箱。
- @mgsam显然,这种用例对于这里的To方法没有意义,因为它只在继承层次结构中转换,而对于值类型,这种转换无论如何都涉及装箱。当然,整个想法更具理论性而非严肃性。
- 当然。To<>扩展不适用于预定义值类型之间的转换,例如缩小整数类型,也不适用于用户定义的转换。因此它只能用于引用转换、装箱转换和取消装箱转换。因此,o是一个盒子也没问题。要么是已经有一个盒子要拆封,要么就是一个盒子即将被创建。当然,同一个盒子是重复使用的。然而,通常Object的扩展方法是一种代码气味,并且在智能意义上产生噪音。
- 这与if (x is SomeType y) y.SomeMethod();的用法有什么关系?
imho,as与null检查结合时才有意义:
1 2 3
| var y = x as T;
if (y != null)
y.SomeMethod(); |
使用"as"不应用用户定义的转换,而强制转换将在适当的情况下使用它们。在某些情况下,这可能是一个重要的区别。
- 记住这一点很重要。埃里克·利珀特在这里说:blogs.msdn.com/eric lippert/archive/2009/10/08/&hellip;
- 很好的评论,P!不过,如果您的代码依赖于这一区别,我会说您的未来将有一个深夜调试会话。
我在这里写了一点:
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()不同,也许这就是代码作者的意图。
- 我不同意你在最后一句话中对代码作者的推测性辩护。((T)x).Whatever()还表示x不是[打算]空的,我强烈怀疑作者通常关心的是到T的转换是否只在引用或取消绑定转换时发生,或者是否需要用户定义或表示更改转换。毕竟,如果我定义public static explicit operator Foo(Bar b){},那么很明显我的意图是认为Bar与Foo兼容。我很少想避免这种转变。
- 好吧,也许大多数代码的作者不会做出如此细微的区分。我个人可能是,但如果是的话,我会加上一条评论。
我经常看到引用这篇误导性文章作为证据,证明"as"比"casting"更快。
这篇文章中一个更明显的误导性方面是图形,它没有指示正在测量的内容:我怀疑它测量的是失败的强制转换(其中"as"明显更快,因为没有异常被抛出)。
如果你花时间进行测量,那么你会发现,正如你所期望的那样,施法比施法成功时的"as"快。
我怀疑这可能是"货物崇拜"使用as关键字而不是cast的一个原因。
- 谢谢你的链接,这很有趣。从我对这篇文章的理解来看,他确实比较了非例外情况。然而,这篇文章是为.NET 1.1写的,评论指出.NET 2.0中的这一变化:性能现在几乎相等,前缀转换甚至更快。
- 这篇文章确实意味着他正在比较非异常情况,但我很久以前做了一些测试,并且无法复制他声称的结果,即使是使用.NET 1.x。而且由于文章没有提供用于运行基准测试的代码,所以不可能说出要比较的内容。
- "货物崇拜"-完美。查看"货物邪教科学理查德费曼"的完整信息。
直接强制转换需要一对括号,而不是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; |
- 正如你所知道的,你所提到的JavaScript语法在C++中也是允许的。
- @pddy:虽然它不是一个直接的100%兼容的替代语法,但并不打算这样(operator x vs conversion constructor)
- 我更希望它使用EDOCX1的C++语法(13)(类似)。你做了件难看的事,看起来应该很难看。
当我使用"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 ;
} |
- 您刚刚描述了as的正确用例。其他1%是什么?
- 打字错了?=)我的意思是99%的时间我使用这个精确的代码片段,而有时我可能在方法调用或其他地方使用"as"。
- 噢,那这比第二个流行的答案有用吗????
- +我同意,说出这一点和鲁本斯·法里亚斯的回答一样有价值——人们希望来到这里,这将是一个有用的例子。
这一定是我最大的毛病之一。
stroustrup的d&e和/或一些我现在找不到的博客文章讨论了to操作符的概念,它可以解决https://stackoverflow.com/users/73070/johannes-rossel(即与as相同的语法,但使用DirectCast语义)所提出的问题。
之所以没有实现这一点,是因为强制转换会导致疼痛和难看,所以您会被推离使用它。
遗憾的是,"聪明的"程序员(通常是书作者(Juval Lowy Irc))通过滥用EDCOX1第1条来绕过这个问题(C++不提供EDOCX1×1),可能是因为这个原因。
即使是vb在统一的语法上也有更大的一致性,它迫使您选择TryCast或DirectCast,并做出决定!
- + 1。您可能是指DirectCast行为,而不是语法。
- @海因茨:TA代表+1。好点。决定做个聪明人,用semantics代替:p
- 由于C语言没有与C、C++或Java兼容的伪装,我发现自己对从这些语言中借用的一些东西感到恼火。它超越了"我知道这是一个x","我知道这不是一个x,但可以表示为一个",到"我知道这不是一个x,可能不是真正可以表示为一个x,但无论如何给我一个x。"我可以看到如果double不代表一个可以在Int32中适用的确切值,那么double-to-int的强制转换将失败。但是,拥有(int)-1.5的收益率-1简直是丑陋的。
- @是的,但是语言设计并不容易,尽管我们都知道——看看C nullables中涉及的一组权衡。唯一已知的解药是定期阅读C的深度版本,因为他们来了:)谢天谢地,我更关心了解F的细微差别这些天,它是更多的理智围绕这些事情。
- @Rubenbartelink:我不太清楚可以为空的类型应该解决什么确切的问题,但我认为在大多数情况下,使用MaybeValid和两个公共字段IsValid和Value,哪种代码可以在它认为合适的情况下使用会更好。这就允许了,如MaybeValid TryGetValue(TKey key) { var ret = default(MaybeValid); ret.IsValid = dict.TryGetValue(key, out ret.Value); return ret; }。与Nullable相比,这不仅节省了至少两个拷贝操作,而且对于任何类型的t都是值得的,而不仅仅是类。
- @Rubenbartelink:我怀疑语言设计中最重要的事情之一(实际上是任何一种设计)是愿意在设计的某一方面回避简化,而这种简化要么在另一方面造成恼人的复杂化,要么给消费者带来容易避免的困难。有时,我认为设计师很容易过度依赖于看似简单、干净和优雅的东西,但实际上,这只是为了防止在可以处理的地方处理一些本应是轻微复杂的事情。喜欢聊天吗?
人们如此喜欢as是因为它让他们觉得不受例外的影响…就像盒子上的保证。一个家伙在盒子上放了一个精美的保证,因为他想让你感觉到里面的温暖和烘烤。你觉得你晚上把那个小盒子放在枕头下面,担保仙女可能会下来留下一角五分之一,对吗,泰德?
回到主题…使用直接强制转换时,可能存在无效强制转换异常。因此,人们将as作为一个整体解决方案来满足他们所有的铸造需求,因为as本身不会抛出一个例外。但有趣的是,在您给(x as T).SomeMethod();的示例中,您将一个无效的强制转换异常与一个空引用异常进行交易。当您看到异常时,它会混淆真正的问题。
我一般不会用太多的东西。我更喜欢is测试,因为在我看来,它比尝试强制转换和检查空值更具可读性和意义。
- "I prefer the is test"-"is"后跟一个强制转换当然比"as"后面跟一个空的测试慢(就像"idictionary.containsKey"后面跟用索引器取消引用比"idictionary.tryGetValue"慢)。但是如果你发现它更易读,毫无疑问,这种差异很少有意义。
- 中间部分的重要陈述是人们如何将as作为一个整体解决方案,因为它使他们感到安全。
我相信,as关键字可以被认为是EDCOX1,18来自C++。
- 我认为C语言中的一个直接转换更像是C++中的EDCOX1 10。
- 我认为C语言中的直线投射更类似于C++中的静态映射。
- @它只返回带有指针的空值。如果可能的话,应该使用引用,它将抛出std::bad_cast。
- @安德鲁·加里森:static_cast不执行运行时类型检查。在C中没有类似的演员表。
- 可悲的是,我不知道你甚至可以在引用上使用casts,因为我只在指针上使用过它们,但是p daddy是绝对正确的!
- @爸爸:忘记了,你是100%正确的(这是我用来验证的文章)
使用"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
} |
- 如果你有种族条件的话,你会遇到更大的问题(但是同意这是一个很好的例子,和其他人一起去,所以+1
- -1因为这使谬论永存。如果其他线程可以更改obj的类型,那么您仍然有问题。声明"//still works"不太可能成立,因为t将用作指向t的指针,但它指向的内存不再是t。当另一个线程在操作(t)进行中更改obj类型时,两个解决方案都不起作用。
- @史蒂芬:你好像很困惑。更改obj的类型意味着更改obj变量本身以保存对另一个对象的引用。它不会改变内存的内容,内存中驻留的对象最初由obj引用。这个原始对象将保持不变,并且t变量仍将保持对它的引用。
- infoq.com/news/2010/01/cds-字典
- @爸爸-我认为你是对的,我错了:如果obj从t对象反弹到t2对象,那么t仍然指向旧的t对象。因为T仍然引用旧对象,所以不能对其进行垃圾收集,所以旧T对象将保持有效。我的竞争条件检测器电路在C++上被训练,其中使用DyrimeSkyCube的类似代码将是一个潜在的问题。
它可能更受欢迎,因为没有技术原因,但只是因为它更容易阅读和更直观。(不说只想回答问题就更好了)