关于C#:cast与as操作符

Direct casting vs 'as' operator?

请考虑以下代码:

1
2
3
4
5
6
7
8
9
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
}

这三种类型的角色扮演有什么区别(好吧,第三种不是角色扮演,但是你有这个意图)。应该首选哪一个?


1
string s = (string)o; // 1

如果o不是string则抛出invalidcastexception。否则,即使onull,也要将o转让给s

1
string s = o as string; // 2

如果o不是stringonull时,将null转让给s。因此,不能将其与值类型一起使用(在这种情况下,运算符永远不能返回null)。否则,将o转让给s

1
string s = o.ToString(); // 3

如果onull则导致nullreferenceexception。无论o是什么类型,都将o.ToString()返回的任何值赋给s

对于大多数转换,使用1-这是简单和简单的。我几乎从不使用2,因为如果某个类型不正确,我通常希望发生异常。我只看到了对这种返回空类型的功能的需要,这些功能使用错误代码(例如,返回空=错误,而不是使用异常)。

3不是强制转换,只是一个方法调用。用于需要非字符串对象的字符串表示形式时。


  • 应该使用时使用肯定是另一回事。
  • 当某物可能是另一个时使用事情。
  • 在你不在乎的时候使用是的,但你只想用可用的字符串表示形式。

  • 这取决于你是否知道o是一个字符串,以及你想用它做什么。如果你的评论意味着o实际上是一个字符串,我更喜欢直接的(string)o类型的演员阵容——不太可能失败。

    使用直接强制转换的最大好处是,当它失败时,您会得到一个InvalidcastException,它几乎告诉您出了什么问题。

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

    1
    2
    3
    4
    5
    6
    string s = o as string;
    if ( s == null )
    {
        // well that's not good!
        gotoPlanB();
    }

    但是,如果不执行该测试,稍后将使用s,并抛出nullreferenceexception。当它们在野外发生时,它们往往更常见,也更难追踪,因为几乎每一行都会取消引用一个变量,并可能抛出一个变量。另一方面,如果要强制转换为值类型(任何基元或日期时间之类的结构),则必须使用直接强制转换-as将不起作用。

    在转换为字符串的特殊情况下,每个对象都有一个ToString,因此如果o不为空,并且您认为ToString方法可能会满足您的需要,那么您的第三个方法可能是可以的。


    如果您已经知道它可以转换为什么类型,请使用C样式转换:

    1
    var o = (string) iKnowThisIsAString;

    注意,只有使用C样式转换才能执行显式类型强制。

    如果您不知道它是否是所需的类型,并且要使用它(如果是),请使用as关键字:

    1
    2
    3
    4
    5
    var s = o as string;
    if (s != null) return s.Replace("_","-");

    //or for early return:
    if (s==null) return;

    注意,as不会调用任何类型转换运算符。只有当对象不是空的并且是指定类型的本机对象时,它才是非空的。

    使用toString()获取任何对象的可读字符串表示形式,即使它不能转换为字符串。


    使用findcontrol方法时,as关键字在ASP.NET中很好。

    1
    2
    3
    4
    5
    Hyperlink link = this.FindControl("linkid") as Hyperlink;
    if (link != null)
    {
         ...
    }

    这意味着您可以对类型化变量进行操作,而不是像使用直接强制转换那样从object强制转换它:

    1
    2
    3
    4
    5
    object linkObj = this.FindControl("linkid");
    if (link != null)
    {
         Hyperlink link = (Hyperlink)linkObj;
    }

    这不是一件大事,但它节省了代码行和变量分配,而且更具可读性


    "as"基于"is",这是一个关键字,用于在运行时检查对象是否与策略兼容(基本上,如果可以进行强制转换),如果检查失败,则返回空值。

    这两个相当:

    使用"AS":

    1
    string s = o as string;

    使用"IS":

    1
    2
    3
    4
    if(o is string)
        s = o;
    else
        s = null;

    相反,C样式的强制转换也在运行时进行,但如果无法进行强制转换,则抛出异常。

    只是为了增加一个重要事实:

    "as"关键字只能用于引用类型。你做不到:

    1
    2
    // I swear i is an int
    int number = i as int;

    在这种情况下,你必须使用铸造。


    2对于强制转换为派生类型很有用。

    假设A是一种动物:

    1
    2
    3
    4
    5
    6
    7
    b = a as Badger;
    c = a as Cow;

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

    将得到一个有最少石膏的饲料。


    "(string)o"将导致无效的强制转换,因为没有直接强制转换。

    "o a s string"将导致s为空引用,而不是引发异常。

    "o.toString()"本身不是任何类型的强制转换,它是由对象实现的方法,因此在某种程度上是由.NET中的每个类实现的,这些类对调用它的类的实例"做些什么",并返回一个字符串。

    别忘了,对于转换为字符串,还有convert.toString(someType instanceOfthatType),其中someType是一组类型中的一种,本质上是框架基类型。


    根据本页的实验结果:http://www.dotnettguru2.org/sebastienros/index.php/2006/02/24/cast_vs_as

    (此页面有时会出现一些"非法引用"错误,因此如果出现错误,只需刷新即可)

    结论是,"as"操作符通常比cast更快。有时快了很多倍,有时快得不到多少。

    我个人认为"as"也更易读。

    因此,由于它既快又"安全"(不会抛出异常),而且可能更容易阅读,所以我建议始终使用"as"。


    所有给出的答案都是好的,如果我可以加上一些:要直接使用字符串的方法和属性(例如ToLower),您不能编写:

    1
    (string)o.ToLower(); // won't compile

    您只能写:

    1
    ((string)o).ToLower();

    但是你可以写:

    1
    (o as string).ToLower();

    as选项更易读(至少在我看来)。


    他们两个在概念上似乎是不同的。

    直接铸造

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

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

    感觉这个物体会被转换成别的东西。

    AS算子

    类型具有直接关系。如:

    • 引用类型:IS-A关系对象总是相同的,只是引用发生了变化。
    • 值类型:复制装箱和可以为空的类型。

    感觉就像你要用不同的方式处理物体。

    样品与IL

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
        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
        }


    1
    string s = o as string; // 2

    优先考虑,因为它避免了双铸的性能损失。


    我想提醒大家注意AS运营商的以下细节:

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

    Note that the as operator performs only reference conversions,
    nullable conversions, and boxing conversions. The as operator can't
    perform other conversions, such as user-defined conversions, which
    should instead be performed by using cast expressions.


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

    1
    String s = String.Concat(o);

    由于没有人提到它,最接近Java的实例的关键字是:

    1
    obj.GetType().IsInstanceOfType(otherObj)

    如果在应用程序的逻辑上下文中,string是唯一有效的类型,请使用direct cast string s = (string) o;。通过这种方法,您将得到InvalidCastException,并实现快速失败的原则。如果使用as运算符,您的逻辑将受到保护,以避免进一步传递无效类型或获取nullreferenceexception。

    如果逻辑需要几个不同类型的强制转换string s = o as string;,并在null上检查它,或使用is运算符。

    新的酷特性出现在C 7.0中以简化转换,检查是模式匹配:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    if(o is string s)
    {
      // Use string variable s
    }

    or

    switch (o)
    {
      case int i:
         // Use int variable i
         break;
      case string s:
         // Use string variable s
         break;
     }


    C支持以下两种类型转换(强制转换):

    γ

    (c)v

    ?在给定表达式中将V的静态类型转换为C

    ?仅当V的动态类型为C或C的子类型时才可能

    ?如果不是,则抛出InvalidcastException

    γ

    V为C

    ?(c)v的非致命变种

    ?因此,将给定表达式中的静态类型v转换为c

    ?如果V的动态类型不是C或C的子类型,则返回空值