在编程接口时,我发现我做了很多强制转换或对象类型转换。

这两种转换方法有什么区别吗?如果是的话,是否有成本差异,或者这对我的程序有什么影响?

public interface IMyInterface
{
    void AMethod();
}

public class MyClass : IMyInterface
{
    public void AMethod()
    {
       //Do work
    }

    // Other helper methods....
}

public class Implementation
{
    IMyInterface _MyObj;
    MyClass _myCls1;
    MyClass _myCls2;

    public Implementation()
    {
        _MyObj = new MyClass();

        // What is the difference here:
        _myCls1 = (MyClass)_MyObj;
        _myCls2 = (_MyObj as MyClass);
    }
}

另外,“一般来说”首选的方法是什么?


当前回答

下面的答案写于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正在做的事情——尽管显然是以一种相当便宜的方式。

其他回答

如果不能执行返回null的转换,As永远不会抛出异常(As仅对引用类型操作)。所以使用as基本上相当于

_myCls2 = _myObj is MyClass ? (MyClass)_myObj : null;

另一方面,c风格的强制转换在无法进行转换时抛出异常。

下面的答案写于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正在做的事情——尽管显然是以一种相当便宜的方式。

请忽略Jon Skeet的建议,re:避免测试-强制转换模式,即:

if (randomObject is TargetType)
{
    TargetType foo = randomObject as TargetType;
    // Do something with foo
}

认为这比强制转换和空测试花费更多的想法是错误的:

TargetType convertedRandomObject = randomObject as TargetType;
if (convertedRandomObject != null)
{
    // Do stuff with convertedRandomObject
}

这是一种不起作用的微观优化。我运行了一些实际的测试,测试-强制转换实际上比强制转换-空比较更快,而且更安全,因为如果强制转换失败,则不可能在if之外的作用域中有空引用。

如果您想知道为什么测试-强制转换更快,或者至少不会更慢,有一个简单而复杂的原因。

简单:即使是简单的编译器也会将两个类似的操作(如test-and-cast)合并为一个测试和分支。强制转换-空测试可能强制执行两个测试和一个分支,一个用于类型测试和失败时转换为空,一个用于空检查本身。至少,它们都将优化为单个测试和分支,因此测试-强制转换既不会比强制转换-空测试慢也不会快。

复杂:为什么测试-强制转换更快:强制转换-空测试将另一个变量引入到外部作用域,编译器必须实时跟踪这个变量,并且它可能无法优化掉这个变量,这取决于你的控制流有多复杂。相反,“测试-强制转换”只在分隔的作用域内引入新变量,这样编译器就知道该变量在作用域退出后失效,从而更好地优化寄存器分配。

所以,请让这个“强制转换-空测试比测试-强制转换更好”的建议死掉吧。请。测试-强制转换既安全又快速。

这是另一个答案,带有一些IL比较。考虑这个类:

public class MyClass
{
    public static void Main()
    {
        // Call the 2 methods
    }

    public void DirectCast(Object obj)
    {
        if ( obj is MyClass)
        { 
            MyClass myclass = (MyClass) obj; 
            Console.WriteLine(obj);
        } 
    } 


    public void UsesAs(object obj) 
    { 
        MyClass myclass = obj as MyClass; 
        if (myclass != null) 
        { 
            Console.WriteLine(obj);
        } 
    }
}

现在看看每种方法产生的IL。即使操作码对您没有任何意义,您也可以看到一个主要的区别-在DirectCast方法中,isinst被调用后是castclass。所以基本上是两次调用而不是一次。

.method public hidebysig instance void  DirectCast(object obj) cil managed
{
  // Code size       22 (0x16)
  .maxstack  8
  IL_0000:  ldarg.1
  IL_0001:  isinst     MyClass
  IL_0006:  brfalse.s  IL_0015
  IL_0008:  ldarg.1
  IL_0009:  castclass  MyClass
  IL_000e:  pop
  IL_000f:  ldarg.1
  IL_0010:  call       void [mscorlib]System.Console::WriteLine(object)
  IL_0015:  ret
} // end of method MyClass::DirectCast

.method public hidebysig instance void  UsesAs(object obj) cil managed
{
  // Code size       17 (0x11)
  .maxstack  1
  .locals init (class MyClass V_0)
  IL_0000:  ldarg.1
  IL_0001:  isinst     MyClass
  IL_0006:  stloc.0
  IL_0007:  ldloc.0
  IL_0008:  brfalse.s  IL_0010
  IL_000a:  ldarg.1
  IL_000b:  call       void [mscorlib]System.Console::WriteLine(object)
  IL_0010:  ret
} // end of method MyClass::UsesAs

isinst关键字与castclass的对比

这篇博文对这两种方法进行了比较。他的总结是:

在直接比较中,isinst比castclass快(尽管只有一点点) 当必须执行检查以确保转换成功时,isinst明显比castclass快 不应该使用isinst和castclass的组合,因为这比最快的“安全”转换慢得多(慢12%以上)

我个人总是使用a,因为它易于阅读,并且是。net开发团队(或者Jeffrey Richter)推荐的。

OP的问题仅限于特定的选角情况。标题涵盖了更多的情况。 以下是我目前能想到的所有相关选角情况的概述:

private class CBase
{
}

private class CInherited : CBase
{
}

private enum EnumTest
{
  zero,
  one,
  two
}

private static void Main (string[] args)
{
  //########## classes ##########
  // object creation, implicit cast to object
  object oBase = new CBase ();
  object oInherited = new CInherited ();

  CBase oBase2 = null;
  CInherited oInherited2 = null;
  bool bCanCast = false;

  // explicit cast using "()"
  oBase2 = (CBase)oBase;    // works
  oBase2 = (CBase)oInherited;    // works
  //oInherited2 = (CInherited)oBase;   System.InvalidCastException
  oInherited2 = (CInherited)oInherited;    // works

  // explicit cast using "as"
  oBase2 = oBase as CBase;
  oBase2 = oInherited as CBase;
  oInherited2 = oBase as CInherited;  // returns null, equals C++/CLI "dynamic_cast"
  oInherited2 = oInherited as CInherited;

  // testing with Type.IsAssignableFrom(), results (of course) equal the results of the cast operations
  bCanCast = typeof (CBase).IsAssignableFrom (oBase.GetType ());    // true
  bCanCast = typeof (CBase).IsAssignableFrom (oInherited.GetType ());    // true
  bCanCast = typeof (CInherited).IsAssignableFrom (oBase.GetType ());    // false
  bCanCast = typeof (CInherited).IsAssignableFrom (oInherited.GetType ());    // true

  //########## value types ##########
  int iValue = 2;
  double dValue = 1.1;
  EnumTest enValue = EnumTest.two;

  // implicit cast, explicit cast using "()"
  int iValue2 = iValue;   // no cast
  double dValue2 = iValue;  // implicit conversion
  EnumTest enValue2 = (EnumTest)iValue;  // conversion by explicit cast. underlying type of EnumTest is int, but explicit cast needed (error CS0266: Cannot implicitly convert type 'int' to 'test01.Program.EnumTest')

  iValue2 = (int)dValue;   // conversion by explicit cast. implicit cast not possible (error CS0266: Cannot implicitly convert type 'double' to 'int')
  dValue2 = dValue;
  enValue2 = (EnumTest)dValue;  // underlying type is int, so "1.1" beomces "1" and then "one"

  iValue2 = (int)enValue;
  dValue2 = (double)enValue;
  enValue2 = enValue;   // no cast

  // explicit cast using "as"
  // iValue2 = iValue as int;   error CS0077: The as operator must be used with a reference type or nullable type
}