考虑以下代码:

void Handler(object o, EventArgs e)
{
   // I swear o is a string
   string s = (string)o; // 1
   //-OR-
   string s = o as string; // 2
   // -OR-
   string s = o.ToString(); // 3
}

三种类型的施法有什么区别(好吧,第三种不是施法,但你明白了意图)。应该首选哪一种?


当前回答

2可用于转换为派生类型。

假设a是动物:

b = a as Badger;
c = a as Cow;

if (b != null)
   b.EatSnails();
else if (c != null)
   c.EatGrass();

将得到最少的投喂。

其他回答

看起来他们两个在概念上是不同的。

直接铸造

类型不必严格相关。它有各种口味。

自定义隐式/显式转换:通常会创建一个新对象。值类型隐式:复制而不丢失信息。值类型显式:副本和信息可能会丢失。IS-A关系:更改引用类型,否则引发异常。相同类型:“铸造是多余的”。

感觉对象将被转换为其他对象。

AS操作员

类型有直接关系。如:

引用类型:IS-A关系对象始终相同,只是引用更改。值类型:复制装箱和可为null的类型。

感觉就像你将以不同的方式处理对象。

样品和IL

    class TypeA
    {
        public int value;
    }

    class TypeB
    {
        public int number;

        public static explicit operator TypeB(TypeA v)
        {
            return new TypeB() { number = v.value };
        }
    }

    class TypeC : TypeB { }
    interface IFoo { }
    class TypeD : TypeA, IFoo { }

    void Run()
    {
        TypeA customTypeA = new TypeD() { value = 10 };
        long longValue = long.MaxValue;
        int intValue = int.MaxValue;

        // Casting 
        TypeB typeB = (TypeB)customTypeA; // custom explicit casting -- IL:  call class ConsoleApp1.Program/TypeB ConsoleApp1.Program/TypeB::op_Explicit(class ConsoleApp1.Program/TypeA)
        IFoo foo = (IFoo)customTypeA; // is-a reference -- IL: castclass  ConsoleApp1.Program/IFoo

        int loseValue = (int)longValue; // explicit -- IL: conv.i4
        long dontLose = intValue; // implict -- IL: conv.i8

        // AS 
        int? wraps = intValue as int?; // nullable wrapper -- IL:  call instance void valuetype [System.Runtime]System.Nullable`1<int32>::.ctor(!0)
        object o1 = intValue as object; // box -- IL: box [System.Runtime]System.Int32
        TypeD d1 = customTypeA as TypeD; // reference conversion -- IL: isinst ConsoleApp1.Program/TypeD
        IFoo f1 = customTypeA as IFoo; // reference conversion -- IL: isinst ConsoleApp1.Program/IFoo

        //TypeC d = customTypeA as TypeC; // wouldn't compile
    }

我想提请注意as操作员的以下细节:

https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/as

注意,as运算符仅执行参考转换,可空转换和装箱转换。as运算符不能执行其他转换,例如用户定义的转换应改为使用强制转换表达式来执行。

string s = o as string; // 2

首选,因为它避免了双重铸造的性能损失。

这真的取决于你是否知道o是否是一个字符串,以及你想用它做什么。如果你的评论意味着o真的是一个串,我更喜欢直接(字符串)o转换-它不太可能失败。

使用直接转换的最大优点是,当它失败时,您会得到一个InvalidCastException,它会告诉您发生了什么错误。

使用as运算符,如果o不是字符串,那么s将设置为null,这在您不确定并想测试s时很方便:

string s = o as string;
if ( s == null )
{
    // well that's not good!
    gotoPlanB();
}

但是,如果不执行该测试,则稍后将使用s并引发NullReferenceException。一旦它们在野外发生,它们往往更常见,也更难追踪,因为几乎每一行都会取消引用一个变量,并可能抛出一个变量。另一方面,如果您试图转换为值类型(任何基元,或DateTime等结构),则必须使用直接转换-as无法工作。

在转换为字符串的特殊情况下,每个对象都有一个ToString,因此如果o不为空,并且您认为ToString方法可以执行您想要的操作,则第三个方法可能是正确的。

当试图获取任何(任何类型)可能为空的字符串表示时,我更喜欢下面的代码行。它很紧凑,它调用ToString(),并正确处理空值。如果o为空,则s将包含String.Empty。

String s = String.Concat(o);