转换与在 CLR 中使用“as"关键字 [英] Casting vs using the 'as' keyword in the CLR

查看:42
本文介绍了转换与在 CLR 中使用“as"关键字的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

When programming interfaces, I've found I'm doing a lot of casting or object type conversion.

这两种转换方式有区别吗?如果是,是否存在费用差异或这对我的计划有何影响?

Is there a difference between these two methods of conversion? If so, is there a cost difference or how does this affect my program?

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

此外,什么是一般"的首选方法?

Also, what is "in general" the preferred method?

推荐答案

线下的回答写于2008年.

The answer below the line was written in 2008.

C# 7 引入了模式匹配,它在很大程度上取代了 as 运算符,您现在可以这样编写:

C# 7 introduced pattern matching, which has largely replaced the as operator, as you can now write:

if (randomObject is TargetType tt)
{
    // Use tt here
}

请注意,tt 仍在此之后的范围内,但并未明确分配.(它 肯定在 if 主体内赋值.)在某些情况下这有点烦人,所以如果你真的关心在每个作用域中引入尽可能少的变量,你可能仍然想使用 is 后跟一个强制转换.

Note that tt is still in scope after this, but not definitely assigned. (It is definitely assigned within the if body.) That's slightly annoying in some cases, so if you really care about introducing the smallest number of variables possible in every scope, you might still want to use is followed by a cast.

我认为到目前为止(在开始这个答案时!)的任何答案都没有真正解释在哪里值得使用.

I don't think any of the answers so far (at the time of starting this answer!) have really explained where it's worth using which.

  • 不要这样做:

  • Don't do this:

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

这不仅要检查两次,而且如果 randomObject 是一个字段而不是一个局部变量,它可能会检查不同的东西.如果另一个线程在两者之间更改了 randomObject 的值,则if"可能会通过但随后转换会失败.

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.

如果 randomObject 真的应该TargetType 的一个实例,即如果不是,那就意味着有一个错误,然后进行转换是正确的解决方案.这会立即抛出异常,这意味着在不正确的假设下不再进行任何工作,并且异常正确地显示了错误的类型.

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;

  • 如果 randomObject mightTargetType 的实例并且 TargetType 是引用类型,那么使用这样的代码:

  • 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
    }
    

  • 如果 randomObject mightTargetType 的实例并且 TargetType 是值类型,那么我们不能将 asTargetType 本身一起使用,但我们可以使用可空类型:

  • 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
    }
    

    (注意:目前这是 实际上比 + cast 慢.我认为它更优雅和一致,但我们走了.)

    (Note: currently this is actually slower than is + cast. I think it's more elegant and consistent, but there we go.)

    如果你真的不需要转换后的值,但你只需要知道它是否一个TargetType的实例,那么is操作符是你的朋友.在这种情况下,TargetType 是引用类型还是值类型并不重要.

    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.

    可能还有其他涉及泛型的情况,其中 is 很有用(因为您可能不知道 T 是否是引用类型,因此您不能使用 as)但是它们'比较模糊.

    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.

    我之前几乎可以肯定地将 is 用于值类型的情况,没有想过将可空类型和 as 一起使用:)

    I've almost certainly used is for the value type case before now, not having thought of using a nullable type and as together :)

    请注意,除了值类型的情况外,以上都没有谈到性能,在这种情况下,我注意到取消装箱为可空值类型实际上更慢 - 但保持一致.

    Note that none of the above talks about performance, other than the value type case, where I've noted that unboxing to a nullable value type is actually slower - but consistent.

    根据 naasking 的回答,is-and-cast 或 is-and-as 都与现代 JIT 的 as-and-null-check 一样快,如下面的代码所示:

    As per naasking's answer, is-and-cast or is-and-as are both as fast as as-and-null-check with modern JITs, as shown by the code below:

    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 毫秒内执行.需要注意的两点:

    On my laptop, these all execute in about 60ms. Two things to note:

    • 它们之间没有显着差异.(实际上,在某些情况下 as-plus-null-check 肯定 较慢.上面的代码实际上使类型检查变得容易,因为它是针对密封类的;如果您正在检查一个界面,余额略微提示支持 as-plus-null-check.)
    • 他们都疯狂快.这只是不会成为您代码中的瓶颈,除非您之后真的不打算对这些值做任何.
    • There's no significant difference between them. (In fact, there are situations in which the as-plus-null-check definitely is slower. The above code actually makes the type check easy because it's for a sealed class; if you're checking for an interface, the balance tips slightly in favour of as-plus-null-check.)
    • They're all insanely fast. This simply will not be the bottleneck in your code unless you really aren't going to do anything with the values afterwards.

    所以让我们不要担心性能.让我们担心正确性和一致性.

    So let's not worry about the performance. Let's worry about correctness and consistency.

    我认为 is-and-cast(或 is-and-as)在处理变量时都是不安全的,因为它所指的值的类型可能会由于测试和强制转换之间的另一个线程而改变.这将是一种非常罕见的情况 - 但我宁愿有一个我可以一致使用的约定.

    I maintain that is-and-cast (or is-and-as) are both unsafe when dealing with variables, as the type of the value it refers to may change due to another thread between the test and the cast. That would be a pretty rare situation - but I'd rather have a convention which I can use consistently.

    我还认为 as-then-null-check 可以更好地分离关注点.我们有一个尝试转换的语句,然后是一个使用结果的语句.is-and-cast 或 is-and-as 执行一个测试,然后然后再次尝试转换值.

    I also maintain that the as-then-null-check gives a better separation of concerns. We have one statement which attempts a conversion, and then one statement which uses the result. The is-and-cast or is-and-as performs a test and then another attempt to convert the value.

    换句话说,有人会曾经写过:

    To put it another way, would anyone ever write:

    int value;
    if (int.TryParse(text, out value))
    {
        value = int.Parse(text);
        // Use value
    }
    

    这就是 is-and-cast 正在做的事情——尽管显然是以一种相当便宜的方式.

    That's sort of what is-and-cast is doing - although obviously in a rather cheaper way.

    这篇关于转换与在 CLR 中使用“as"关键字的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

  • 查看全文
    登录 关闭
    扫码关注1秒登录
    发送“验证码”获取 | 15天全站免登陆