即使没有空检查,使用“ as”代替强制转换是否有意义?[关闭]


351

在开发博客,在线代码示例,以及(最近)甚至是一本书中,我一直对类似的代码code之以鼻:

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

或更糟糕的是:

(x as T).SomeMethod();

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

我是唯一认为这是公然滥用as关键字的人吗?还是我错过了明显的事情,而上述模式确实有意义?


56
看起来会更有趣(吻为S)。SteveIsSuchA(); 但我同意,这是一种虐待。
SwDevMan81 2010年

5
比写东西要酷得多((T)x).SomeMethod(),不是吗?;)(开个玩笑,您当然是对的!)
Lucero 2010年

8
@P Daddy我不同意,这是一个很好的问题(此代码模式确实有意义),并且非常有用。对问题+1,对任何投票结束的人都皱眉。
MarkJ 2010年

9
Lucerno是正确的,此编码模式是通过尝试避免括号引起的。暴露于Lisp后无法治愈。
汉斯·帕桑

13
优化代码:(f as T).SomeMethod();)
MSalters 2010年

Answers:


252

您的理解是真的。这听起来像是尝试对我进行微优化。确定类型时,应使用常规演员表。除了生成更明智的异常之外,它还会快速失败。如果你错了你对类型的假设,你的程序将立即失败,你就可以看到失败立即,而不是等待的原因一个NullReferenceException或者ArgumentNullException甚至是一个逻辑上的错误在未来的某个时候。通常,as没有在null某处进行检查的表达式就是代码味道。

另一方面,如果不确定转换是否正确,并期望转换失败,则应使用as正常的转换而不是用try-catch块包装。此外,as建议在类型检查后再进行强制类型转换后再使用。代替:

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

其产生的isinst指令is关键字,和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()

5
我认为比赛条件无关紧要。如果遇到此问题,则您的代码不是线程安全的,并且比使用关键字“ as”有更可靠的方法来解决它。其余答案为+1。
RMorrisey

10
@RMorrisey:我至少要记住一个示例:假设您有一个cache对象,另一个线程试图通过将其设置为来使其无效null。在无锁的情况下,可能会出现这种情况。
Mehrdad Afshari 2010年

10
is + cast足以触发来自FxCop的“不要进行不必要的转换”警告:msdn.microsoft.com/en-us/library/ms182271.aspx这应该是避免构建的足够理由。
David Schmitt 2010年

2
您应该避免在上使用扩展方法Object。在值类型上使用该方法将导致不必要地将其装箱。
MgSam 2012年

2
@MgSam显然,这种用例对于To这里的方法没有意义,因为它仅在继承层次结构之间进行转换,对于值类型而言,无论如何都要进行装箱。当然,整个想法是理论上的,而不是严肃的。
Mehrdad Afshari 2012年



36

我在这里写了一些:

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

我明白你的意思。我同意它的主旨:强制转换运算符传达“我确定可以将该对象转换为该类型,如果我错了,我愿意冒例外的风险”,而“按”运算符传达“我不确定该对象是否可以转换为该类型;如果我错了,请给我一个null”。

但是,存在细微的差异。(x表示T)。无论()传达的信息是“我不仅知道x可以转换为T,而且知道这样做仅涉及引用或拆箱转换,而且x不为空”。那确实传达了与((T)x).Whatever()不同的信息,也许正是代码作者想要的。


1
我不同意您在最后一句话中对代码作者的投机性辩护。 ((T)x).Whatever() 传达了x并非[预期为空]的信息,我非常怀疑作者通常会在意要进行的转换T是仅参考转换还是取消装箱转换,或者是否需要用户定义转换或更改表示形式的转换。毕竟,如果我定义了public static explicit operator Foo(Bar b){},那么显然我的意图Bar是与兼容Foo。我很少想避免这种转换。
P爸爸

4
好吧,也许大多数代码作者不会做出这种微妙的区分。我个人可能是,但如果是,我会为此添加评论。
埃里克·利珀特

16

我经常看到对该误导性文章的引用是“按原样”比强制转换更快的证据。

本文最明显的误导性方面之一是图形,该图形未指示正在测量的内容:我怀疑它正在测量失败的强制类型转换(其中“ as”显然要快得多,因为不会引发异常)。

如果花时间进行测量,那么您将看到,如预期的那样,强制转换成功后,强制转换的速度比“ as” 更快

我怀疑这可能是“崇拜”使用as关键字而不是强制转换的原因之一。


2
感谢您的链接,这非常有趣。根据我对本文的理解,他确实比较了非例外情况。尽管如此,本文还是针对.net 1.1编写的,但评论指出,这在.net 2.0中有所改变:性能现在几乎相等,前缀转换甚至更快。
Heinzi 2010年

1
这篇文章确实暗示他正在比较非例外情况,但是很久以前我做了一些测试,即使使用.NET 1.x,也无法重现他声明的结果。并且由于本文未提供用于运行基准测试的代码,因此无法说出正在比较的内容。

“货运邪教”-完美。查看完整的信息“ Cargo Cult Science理查德·费曼”。
鲍勃·丹尼

11

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

不过,同意例外情况。但是至少对我来说,大多数使用as沸腾方法进行null事后检查的方法,我发现比捕获异常更好。


8

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; 
}

8
您刚刚描述了的正确用例as。其他1%是多少?
P爸爸

错字了吗?=)我的意思是我有99%的时间使用此确切的代码段,而有时我可能在方法调用或其他地方使用“ as”。
Max Galkin 2010年

D'oh,这比第二个普遍的答案有用吗???
Max Galkin 2010年

2
+1我同意说出来与鲁本斯·法里亚斯的回答一样有价值-希望人们会来这里,这将是一个有用的例子
Ruben Bartelink 2010年

8

只是因为人们喜欢它的外观,所以它具有很好的可读性。

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

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

或定义一个to运算符,强制转换等效于as

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

大家知道,您提到的JavaScript语法在C ++中也是允许的。
P爸爸

@PDaddy:它不是直接兼容100%的替代语法,并且不旨在如此(运算符X与转换构造函数)
Ruben Bartelink 2010年

我希望它使用dynamic_cast<>()(和类似名称)的C ++语法。您做的事情很丑,看起来应该很丑。
Tom Hawtin-大头钉

5

人们as之所以如此喜欢,是因为它使他们对例外情况感到放心...就像盒子上的保证。一个男人在盒子上放了一个花哨的保证,因为他想让你感觉到里面所有的温暖和烤面包。你以为晚上把那个小盒子放在枕头底下,保证仙子可能会掉下来离开四分之一,我是泰德吗?

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

我通常不会使用as太多。我更喜欢is测试,因为对我而言,它看起来更可读,更有意义,然后尝试进行强制转换并检查是否为空。


2
“我更喜欢is测试”-“ is”,然后进行强制转换当然比“ as”慢,然后进行null测试(就像“ IDictionary.ContainsKey”,然后使用索引器取消引用比“ IDictionary.TryGetValue”慢) ”)。但是,如果您发现它更具可读性,则毫无疑问,两者之间的差异几乎不会很大。

中间的重要声明是人们如何as作为一个整体解决方案应用,因为它使他们感到安全。
鲍勃

5

这一定是我最讨厌的事情之一。

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

未能实现的原因是,强制转换会导致疼痛和丑陋,因此您被迫放弃使用它。

可惜的是,“聪明的”程序员(通常是书的作者(Juval Lowy IIRC))as以这种方式滥用(C ++不提供as,可能是出于这个原因)来绕开它。

即使VB在具有统一的语法更加一致,迫使你选择一个TryCastDirectCast让你的心


+1。您可能是指DirectCast 行为,而不是语法
Heinzi 2010年

@Heinzi:Ta为+1。好点子。决定是一个聪明人,并semantics改为使用:P
Ruben Bartelink 2010年

鉴于C#不具有与C,C ++或Java的兼容性,我发现自己对从这些语言中借来的某些东西感到不满。它超出了“我知道这是一个X”和“我知道这不是一个X,但可以表示为一个”,超出了“我知道这不是X,并且可能无法真正表示为一个”。 ,但还是给我一个X。” 我可以看到对于double-to-int如果double强制转换不能代表可以适合的确切值而失败的强制转换的有用性Int32,但是(int)-1.5收益-1简直很难看。
supercat 2014年

@supercat是的,但是众所周知,语言设计并不容易-查看C#可为空所涉及的一系列折衷方案。唯一已知的解毒剂是,随着发行的深入,定期阅读C#:)幸运的是,这些天我更关心了解F#的细微差别,并且在许多事情上更加理智。
Ruben Bartelink

@RubenBartelink:我不太清楚可以为空的类型应该解决什么确切的问题,但是我认为在大多数情况下,MaybeValid<T>具有两个公共字段IsValid以及Value在合适的情况下可以处理哪些代码会更好。那将允许例如MaybeValid<TValue> TryGetValue(TKey key) { var ret = default(MaybeValid<TValue>); ret.IsValid = dict.TryGetValue(key, out ret.Value); return ret; }。与相比,这不仅可以节省至少两个复制操作Nullable<T>,而且对于任何类型(T不仅仅是类)都值得。
supercat 2014年

2

我相信该as关键字可以被认为是dynamic_castC ++中外观更优雅的版本 。


我想说C#中的直接转换更像dynamic_castC ++中的。
P爸爸

我认为C#中的直接转换更等效于C ++中的static_cast。
Andrew Garrison 2010年

2
@Ruben Bartelink:它仅返回带指针的null。使用引用(可能会使用),它会引发std::bad_cast
P爸爸

1
@Andrew Garrison:不static_cast执行任何运行时类型检查。C#中没有与此类似的强制转换。
P爸爸

可悲的是,我不知道您甚至可以在引用上使用强制类型转换,因为我只在指针上使用过强制类型转换,但是P Daddy绝对正确!
Andrew Garrison 2010年

1

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


1

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

2
如果您在这个丑陋的比赛中遇到了问题,那么您会遇到更大的问题(但同意与其他人一起参加比赛是一个不错的例子,所以+1
Ruben Bartelink 2010年

-1,因为这会导致谬误。如果其他线程可以更改obj的类型,那么您仍然有问题。声明“ //仍然有效”很难成立,因为t将用作指向T的指针,但它指向的内存不再是T。当另一个线程更改类型时,这两种解决方案都不会起作用。动作(t)进行时的obj。
Stephen C. Steel

5
@Stephen C. Steel:您似乎很困惑。更改的类型obj将意味着更改obj变量本身以保留对另一个对象的引用。不会更改最初由引用的对象所在的内存内容obj。该原始对象将保持不变,并且该t变量仍将保留对其的引用。
P爸爸


1
@P Daddy-我认为您是对的,但我错了:如果obj从T对象反弹到T2对象,则t仍将指向旧的T对象。由于t仍引用旧对象,因此无法对其进行垃圾回收,因此旧T对象将保持有效。我的比赛条件检测器电路是在C ++上训练的,其中使用dynamic_cast的类似代码可能会引起问题。
Stephen C. Steel
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.