C# 转换与在 CLR 中使用“as”关键字

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/496096/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-04 05:40:04  来源:igfitidea点击:

Casting vs using the 'as' keyword in the CLR

c#castingclr

提问by Frank V

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?

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

采纳答案by Jon Skeet

The answer below the line was written in 2008.

线下的答案写于2008年。

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

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

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

Note that ttis still in scope after this, but not definitely assigned. (It isdefinitely assigned within the ifbody.) 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 isfollowed by a cast.

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



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
    }
    

    Not only is this checking twice, but it may be checking different things, if randomObjectis 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 randomObjectbetween the two.

  • If randomObjectreally shouldbe 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 randomObjectmightbe an instance of TargetTypeand TargetTypeis a reference type, then use code like this:

    TargetType convertedRandomObject = randomObject as TargetType;
    if (convertedRandomObject != null)
    {
        // Do stuff with convertedRandomObject
    }
    
  • If randomObjectmightbe an instance of TargetTypeand TargetTypeis a value type, then we can't use aswith TargetTypeitself, 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 isan instance of TargetType, then the isoperator 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 isis 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 isfor the value type case before now, not having thought of using a nullable type and astogether :)

  • 不要这样做:

    // 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可能TargetTypeandTargetType是值类型的实例,那么我们不能使用asTargetType自己,但我们可以使用可空类型:

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

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

  • 如果您真的不需要转换后的值,而只需要知道它是否TargetType 的实例,那么is运算符就是您的朋友。在这种情况下,TargetType 是引用类型还是值类型并不重要。

  • 可能还有其他情况涉及泛型 whereis有用(因为您可能不知道 T 是否是引用类型,因此您不能使用 as)但它们相对模糊。

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



EDIT: 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.

编辑:请注意,除了值类型的情况外,以上都没有谈到性能,在那里我注意到拆箱为可空值类型实际上更慢 - 但一致。

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:

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

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

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

在我的笔记本电脑上,这些都在大约 60 毫秒内执行。有两点需要注意:

  • There's no significant difference between them. (In fact, there are situations in which the as-plus-null-check definitely isslower. 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 insanelyfast. This simply will notbe the bottleneck in your code unless you really aren't going to do anythingwith the values afterwards.
  • 它们之间没有显着差异。(实际上,在某些情况下,as-plus-null-check 肯定更慢。上面的代码实际上使类型检查变得容易,因为它是针对密封类的;如果您正在检查接口,则平衡会略微提示赞成 as-plus-null-check。)
  • 他们都快疯了。这根本不会成为您代码中的瓶颈,除非您之后真的不打算对这些值做任何事情

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

所以我们不用担心性能。让我们担心正确性和一致性。

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.

我认为 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 thenanother attempt to convert the value.

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

To put it another way, would anyone everwrite:

换个方式,会有人写:

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

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

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

回答by Patrick Desjardins

"as"will return NULL if not possible to cast.

如果无法转换,“as”将返回 NULL。

casting beforewill raise an exception.

铸造 before将引发异常。

For the performance, raising an exception is usually more costly in time.

对于性能而言,引发异常通常会在时间上花费更多。

回答by TheSmurf

If the cast fails, the 'as' keyword doesn't throw an exception; it sets the variable to null (or to its default value for value types) instead.

如果转换失败,'as' 关键字不会抛出异常;而是将变量设置为 null(或值类型的默认值)。

回答by Anton Gogolev

asnever throws an exception if it cannot perform the conversion returning nullinstead (asoperates on reference types only). So using asis basically equivalent to

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

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

C-style casts, on the other hand, throw an exception when no conversion is possible.

另一方面,C 样式的强制转换在无法进行转换时抛出异常。

回答by Cerebrus

The askeyword works the same as an explicit cast between compatible reference types with the major difference that it does not raise an exception if conversion fails. Rather, it yields a null value in the target variable. Since Exceptions are very expensive in terms of performance, it is considered a much better method of casting.

as关键字的工作方式与兼容引用类型之间的显式转换相同,主要区别在于如果转换失败,它不会引发异常。相反,它在目标变量中产生一个空值。由于异常在性能方面非常昂贵,因此它被认为是一种更好的铸造方法。

回答by toad

Not really an answer to your question, but what I think is an important related point.

不是你问题的真正答案,但我认为是一个重要的相关点。

If you are programming to an interface you shouldn't be needing to cast. Hopefully these casts are very rare. If not you likely need to rethink some of your interfaces.

如果您正在为接口编程,则不需要强制转换。希望这些演员阵容非常罕见。如果不是,您可能需要重新考虑您的某些接口。

回答by Jeffrey L Whitledge

The asoperator can only be used on reference types, it cannot be overloaded, and it will return nullif the operation fails. It will never throw an exception.

as运算符只能用于引用类型,不能重载,null如果操作失败将返回。它永远不会抛出异常。

Casting can be used on any compatible types, it can be overloaded, and it will throw an exception if the operation fails.

转换可以用于任何兼容的类型,可以重载,如果操作失败,它将抛出异常。

The choice of which to use depends on the circumstances. Primarily, it's a matter of whether you want to throw an exception on a failed conversion.

选择使用哪个取决于具体情况。首先,这取决于您是否要在转换失败时抛出异常。

回答by Darryl Braaten

It depends, do you want to check for null after using "as" or would you prefer your app to throw an exception?

这取决于,您想在使用“as”后检查 null 还是希望您的应用程序抛出异常?

My rule of thumb is if I always expect the variable to be of the type I am expecting at the time I want I use a cast. If it is possible that the variable will not cast to what I want and I am prepared to handle nulls from using as, I will use as.

我的经验法则是,如果我总是期望变量是我想要使用强制转换时所期望的类型。如果变量可能无法转换为我想要的值并且我准备使用 as 处理空值,我将使用 as。

回答by f3lix

This is not an answer to the question but comment to the question's code example:

这不是问题的答案,而是对问题代码示例的评论:

Usually you should not have to cast an Object from e.g. IMyInterface to MyClass. The great thing about interfaces is that if you take an object as input that implements an interface, than you don't have to care what kind of object you are getting.

通常,您不必将对象从例如 IMyInterface 强制转换为 MyClass。接口的伟大之处在于,如果你将一个对象作为实现接口的输入,那么你就不必关心你得到的是什么类型的对象。

If you cast IMyInterface to MyClass, than you already assume that you get an object of type MyClass and it makes no sense to use IMyInterface, because if you feed your code with other classes that implement IMyInterface, it would break your code...

如果您将 IMyInterface 转换为 MyClass,那么您已经假设您获得了一个 MyClass 类型的对象,并且使用 IMyInterface 是没有意义的,因为如果您将代码与其他实现 IMyInterface 的类一起提供,它会破坏您的代码......

Now, my advice: if your interfaces are well designed you can avoid a lot of typecasting.

现在,我的建议是:如果你的界面设计得很好,你可以避免大量的类型转换。

回答by Patrik H?gne

One of the more subtle differences between the two is that the "as" keyword can not be used for casting when a cast operator is involved:

两者之间更细微的区别之一是,当涉及强制转换运算符时,“as”关键字不能用于强制转换:

public class Foo
{
    public string Value;

    public static explicit operator string(Foo f)
    {
        return f.Value;
    }

}

public class Example
{
    public void Convert()
    {
        var f = new Foo();
        f.Value = "abc";

        string cast = (string)f;
        string tryCast = f as string;
    }
}

This will not compile (although I think it did in previous versions) on the last line since the "as" keywords do not take cast operators into account. The line string cast = (string)f;works just fine though.

这不会在最后一行编译(虽然我认为它在以前的版本中编译过),因为“as”关键字不考虑强制转换运算符。不过这条线string cast = (string)f;工作得很好。