投射vs在CLR中使用'as'关键字

在编程接口时,我发现我正在进行大量的转换或对象类型转换。

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

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后跟铸造。


到目前为止,我认为没有任何答案(在开始这个答案的时候!)已经真正解释了它在哪里值得使用。

  • 不要这样做:

    // Bad code - checks type twice for no reason
    if (randomObject is TargetType)
    {
        TargetType foo = (TargetType) randomObject;
        // Do something with foo
    }
    

    这不仅是检查两次,而且它可能检查不同的事情,如果randomObject是一个字段而不是局部变量。 如果另一个线程更改了这randomObject之间的randomObject值,那么“if”可能会通过,但是会导致转换失败。

  • 如果randomObject真的应该是TargetType一个实例,即如果它不是,那就意味着有一个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;
    
  • 如果randomObject可能是TargetType一个实例并且TargetType是引用类型,那么使用如下代码:

    TargetType convertedRandomObject = randomObject as TargetType;
    if (convertedRandomObject != null)
    {
        // Do stuff with convertedRandomObject
    }
    
  • 如果randomObject可能是TargetType一个实例,并且TargetType是一个值类型,那么我们不能as使用TargetType本身as使用,但我们可以使用一个可为空的类型:

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

    (注意:目前这个速度实际上比“+ cast”更慢,我认为它更加优雅和一致,但是我们走了。)

  • 如果你真的不需要转换的值,但你只需要知道它是否是TargetType的一个实例,那么is运算符就是你的朋友。 在这种情况下,TargetType是引用类型还是值类型都无关紧要。

  • 有可能是涉及仿制药,而其他情况下, is非常有用的(因为你可能不知道T是否是引用类型或没有,所以你不能因为使用),但他们是比较模糊的。

  • 我几乎可以肯定,使用is该值类型的情况下现在之前,没有使用可空类型和思想as共同:)


  • 编辑:请注意,以上没有谈到性能,除了值类型的情况下,我已经注意到拆箱到可空值类型实际上是较慢 - 但一致。

    根据naasking的回答,is-and-cast或is-and-as与现代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);
        }
    }
    

    在我的笔记本电脑上,这些全部在大约60ms内执行。 需要注意两点:

  • 它们之间没有显着差异。 (事实上​​,有些情况下,as-plus-null-check肯定是慢的,上面的代码实际上使得类型检查变得容易,因为它是用于密封类的;如果你正在检查一个接口,天平会略微提示赞成加零检查。)
  • 他们都非常快速。 这根本不会是你的代码中的瓶颈,除非你事后不打算对值做任何事情。
  • 所以,我们不要担心表现。 让我们担心正确性和一致性。

    我认为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);
        // Use value
    }
    

    这就是什么 - 演员在做什么 - 虽然显然是以一种相对便宜的方式。


    “as”将返回NULL,如果无法投射。

    之前的投射会引发异常。

    对于表演来说,举办例外通常会花费更多的时间。


    这是另一个答案,与一些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%以上)
  • 我个人总是使用As,因为它易于阅读,并由.NET开发团队(或Jeffrey Richter)推荐。

    链接地址: http://www.djcxy.com/p/12809.html

    上一篇: Casting vs using the 'as' keyword in the CLR

    下一篇: What's the difference between the 'ref' and 'out' keywords?