该行下方的答案写于2008年。
C#7引入了模式匹配,它已在很大程度上取代了as
运算符,因为您现在可以编写:
if (randomObject is TargetType tt)
{
// Use tt here
}
请注意,tt
此后仍在范围内,但未明确分配。(它肯定是在if
体内分配的。)在某些情况下,这有点令人讨厌,因此,如果您真的很想在每个范围内引入尽可能少的变量,则可能仍要使用is
强制类型转换。
我认为到目前为止(在开始这个答案之时!),没有任何答案能真正说明在哪里使用该答案。
不要这样做:
// Bad code - checks type twice for no reason
if (randomObject is TargetType)
{
TargetType foo = (TargetType) randomObject;
// Do something with foo
}
这不仅要检查两次,而且还可能要检查其他内容(如果randomObject
是字段而不是局部变量)。如果另一个线程更改了randomObject
两者之间的值,则“ if”可能会传递,但强制转换失败。
如果randomObject
确实应该是的实例TargetType
,即不是,则意味着存在错误,那么强制转换是正确的解决方案。这将立即引发异常,这意味着在错误的假设下不会进行更多工作,并且该异常正确显示了错误的类型。
// 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;
如果randomObject
可能是的实例TargetType
并且TargetType
是引用类型,则使用如下代码:
TargetType convertedRandomObject = randomObject as TargetType;
if (convertedRandomObject != null)
{
// Do stuff with convertedRandomObject
}
如果randomObject
可能是的实例TargetType
并且TargetType
是值类型,那么我们不能将as
其TargetType
自身使用,但可以使用可为空的类型:
TargetType? convertedRandomObject = randomObject as TargetType?;
if (convertedRandomObject != null)
{
// Do stuff with convertedRandomObject.Value
}
(注意:目前,这实际上比+ cast慢。我认为它更优雅,更一致,但是我们可以了。)
如果您确实不需要转换后的值,但是只需要知道它是否是 TargetType的实例,那么is
操作员就是您的朋友。在这种情况下,TargetType是引用类型还是值类型都没有关系。
在涉及泛型的其他情况下,可能还有其他is
有用的情况(因为您可能不知道T是否是引用类型,因此您不能使用as),但它们相对模糊。
我几乎可以肯定以前使用is
过值类型的情况,还没有考虑过as
一起使用可空类型:)
编辑:请注意,除了值类型的情况外,上述内容均未提及性能,在此情况下,我注意到拆箱为可空值类型实际上较慢-但保持一致。
根据naasking的回答,使用现成的JIT进行“即投即用”或“即按即做”的检查和检查都一样快,如以下代码所示:
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-check。)
- 他们都快疯了。除非您以后真的不对这些值做任何事情,否则这根本不会成为代码中的瓶颈。
因此,让我们不必担心性能。让我们担心正确性和一致性。
我认为,在处理变量时,异变(或异变)都是不安全的,因为它所引用的值的类型可能会由于测试和强制转换之间的另一个线程而改变。那将是一种非常罕见的情况-但我宁愿有一个可以一致使用的约定。
我还坚持认为,那么零检查可以更好地分离关注点。我们有一个语句尝试进行转换,然后有一个语句使用结果。按原样或按原样执行测试,然后再次尝试转换该值。
换个方式,会有人曾写:
int value;
if (int.TryParse(text, out value))
{
value = int.Parse(text);
// Use value
}
那是随便做的事情-尽管显然以一种更便宜的方式。