下面的答案写于2008年。
c# 7引入了模式匹配,它在很大程度上取代了as操作符,正如你现在可以这样写:
if (randomObject is TargetType tt)
{
// Use tt here
}
注意,tt在此之后仍然在范围内,但没有明确分配。(它肯定是在if语句体中赋值的。)在某些情况下,这有点烦人,所以如果您真的关心在每个作用域中引入尽可能少的变量,您可能仍然希望使用is后跟强制类型转换。
我认为到目前为止(在开始回答这个问题的时候!)没有任何答案真正解释了它在哪里值得使用哪个。
Don't do this:
// Bad code - checks type twice for no reason
if (randomObject is TargetType)
{
TargetType foo = (TargetType) randomObject;
// Do something with foo
}
Not only is this checking twice, but it may be checking different things, if randomObject is a field rather than a local variable. It's possible for the "if" to pass but then the cast to fail, if another thread changes the value of randomObject between the two.
If randomObject really should be an instance of TargetType, i.e. if it's not, that means there's a bug, then casting is the right solution. That throws an exception immediately, which means that no more work is done under incorrect assumptions, and the exception correctly shows the type of bug.
// This will throw an exception if randomObject is non-null and
// refers to an object of an incompatible type. The cast is
// the best code if that's the behaviour you want.
TargetType convertedRandomObject = (TargetType) randomObject;
If randomObject might be an instance of TargetType and TargetType is a reference type, then use code like this:
TargetType convertedRandomObject = randomObject as TargetType;
if (convertedRandomObject != null)
{
// Do stuff with convertedRandomObject
}
If randomObject might be an instance of TargetType and TargetType is a value type, then we can't use as with TargetType itself, but we can use a nullable type:
TargetType? convertedRandomObject = randomObject as TargetType?;
if (convertedRandomObject != null)
{
// Do stuff with convertedRandomObject.Value
}
(Note: currently this is actually slower than is + cast. I think it's more elegant and consistent, but there we go.)
If you really don't need the converted value, but you just need to know whether it is an instance of TargetType, then the is operator is your friend. In this case it doesn't matter whether TargetType is a reference type or a value type.
There may be other cases involving generics where is is useful (because you may not know whether T is a reference type or not, so you can't use as) but they're relatively obscure.
I've almost certainly used is for the value type case before now, not having thought of using a nullable type and as together :)
编辑:请注意,上面没有讨论性能,除了值类型的情况,其中我已经注意到,解盒到可空值类型实际上更慢——但一致。
根据naask的回答,is-and-cast或is-and-as都和现代jit中的As -and-null检查一样快,如下所示:
using System;
using System.Diagnostics;
using System.Linq;
class Test
{
const int Size = 30000000;
static void Main()
{
object[] values = new object[Size];
for (int i = 0; i < Size - 2; i += 3)
{
values[i] = null;
values[i + 1] = "x";
values[i + 2] = new object();
}
FindLengthWithIsAndCast(values);
FindLengthWithIsAndAs(values);
FindLengthWithAsAndNullCheck(values);
}
static void FindLengthWithIsAndCast(object[] values)
{
Stopwatch sw = Stopwatch.StartNew();
int len = 0;
foreach (object o in values)
{
if (o is string)
{
string a = (string) o;
len += a.Length;
}
}
sw.Stop();
Console.WriteLine("Is and Cast: {0} : {1}", len,
(long)sw.ElapsedMilliseconds);
}
static void FindLengthWithIsAndAs(object[] values)
{
Stopwatch sw = Stopwatch.StartNew();
int len = 0;
foreach (object o in values)
{
if (o is string)
{
string a = o as string;
len += a.Length;
}
}
sw.Stop();
Console.WriteLine("Is and As: {0} : {1}", len,
(long)sw.ElapsedMilliseconds);
}
static void FindLengthWithAsAndNullCheck(object[] values)
{
Stopwatch sw = Stopwatch.StartNew();
int len = 0;
foreach (object o in values)
{
string a = o as string;
if (a != null)
{
len += a.Length;
}
}
sw.Stop();
Console.WriteLine("As and null check: {0} : {1}", len,
(long)sw.ElapsedMilliseconds);
}
}
在我的笔记本电脑上,这些都在大约60毫秒内执行。有两件事需要注意:
它们之间没有显著差异。(事实上,在某些情况下,as-plus-null检查确实更慢。上面的代码实际上使类型检查变得很容易,因为它是针对密封类的;如果你正在检查一个接口,平衡会略微倾向于as-plus-null检查。)
它们都快得不可思议。这不会成为您代码中的瓶颈,除非您以后真的不打算对这些值做任何事情。
所以我们不用担心性能。让我们担心正确性和一致性。
我认为is-and-cast(或is-and-as)在处理变量时都是不安全的,因为它所引用的值的类型可能会因为测试和强制转换之间的另一个线程而改变。这将是一个相当罕见的情况-但我宁愿有一个惯例,我可以一直使用。
我还认为,“当时为空”检查提供了更好的关注点分离。一个语句尝试进行转换,另一个语句使用转换结果。is-and-cast或is-and-as执行一个测试,然后再次尝试转换值。
换句话说,有人会这样写吗:
int value;
if (int.TryParse(text, out value))
{
value = int.Parse(text);
// Use value
}
这就是is-and-cast正在做的事情——尽管显然是以一种相当便宜的方式。