一尘不染

在CLR中投放与使用'as'关键字

c#

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

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

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);
    }
}

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


阅读 274

收藏
2020-05-19

共1个答案

一尘不染

该行下方的答案写于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是值类型,那么我们不能将asTargetType自身使用,但可以使用可为空的类型:

    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
}

那是随便做的事情-尽管显然以一种更便宜的方式。

2020-05-19