以下回答是在2008年写的。
C# 7引入了模式匹配,这在很大程度上取代了as
运算符,现在你可以这样写:
if (randomObject is TargetType tt)
{
}
请注意,此时
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.
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 :)
注意,以上内容并未涉及性能问题,除了值类型的情况,我已经指出将值类型拆箱为可空值类型实际上更慢 - 但是一致。
根据naasking的答案,现代JIT在以下代码中显示is-and-cast或is-and-as与as-and-null-check一样快:
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加空检查肯定会更慢。上面的代码实际上很容易进行类型检查,因为它是针对一个密封类的; 如果你要检查一个接口,那么as加空检查的平衡会稍微倾向于这种情况。)
- 它们都非常快。除非您真的不打算在之后对值进行任何操作,否则这根本不会成为您代码中的瓶颈。
所以我们不用担心性能问题。让我们关注正确性和一致性。
我坚持认为当处理变量时,is-and-cast(或is-and-as)都不安全,因为它所引用的值的类型可能会在测试和转换之间由于另一个线程而改变。虽然这种情况可能很少见,但我宁愿有一个可以一致使用的约定。
我还坚持认为as-then-null-check给出了更好的责任分离。我们有一个语句尝试进行转换,然后有一个语句使用结果。is-and-cast或is-and-as执行一个测试,然后又尝试转换值。
换句话说,是否有人会写出:
int value;
if (int.TryParse(text, out value))
{
value = int.Parse(text);
}
这有点像 is-and-cast 所做的事情 - 显然是以一种更便宜的方式实现的。