直接铸造与“作为”操作员?

Direct casting vs 'as' operator?

提问人:nullDev 提问时间:9/25/2008 最后编辑:Kirill KobelevnullDev 更新时间:5/26/2019 访问量:215534

问:

请考虑以下代码:

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
}

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

C# 强制转换

评论

2赞 Unsliced 9/25/2008
不完全重复,但在上一个问题中也有一些性能讨论。
13赞 Earth Engine 2/8/2016
第四名: ;第 5 个:(或等效于早期 C# 的形式)string s = Convert.ToString(o)string s = $"{o}"string.Format
0赞 Grisha 8/5/2021
由于许多人可以使用这篇文章作为参考,因此我们也可以从 C# 7 开始使用 IS 运算符进行转换。参考

答:

11赞 Sergio Acosta 9/25/2008 #1

“as”基于“is”,这是一个关键字,用于在运行时检查对象是否在政治上兼容(基本上是是否可以进行强制转换),如果检查失败,则返回 null。

这两者是等效的:

使用“as”:

string s = o as string;

使用“is”:

if(o is string) 
    s = o;
else
    s = null;

相反,c 样式强制转换也是在运行时进行的,但如果无法强制转换,则会引发异常。

只是为了补充一个重要的事实:

“as”关键字仅适用于引用类型。你不能做:

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

在这些情况下,您必须使用铸造。

1014赞 Sander 9/25/2008 #2
string s = (string)o; // 1

如果不是 .否则,即使 是 ,也应分配给 。ostringosonull

string s = o as string; // 2

分配给 if is not a 或 if is 。因此,不能将其与值类型一起使用(在这种情况下,运算符永远无法返回)。否则,将分配给 。nullsostringonullnullos

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

如果为 ,则会导致 NullReferenceException。将返回的任何内容分配给 ,无论类型是什么。onullo.ToString()so


对于大多数转换,请使用 1 - 它简单明了。我几乎从不使用 2,因为如果某些东西的类型不正确,我通常会期望发生异常。我只看到需要这种 return-null 类型的功能,以及使用错误代码(例如返回 null = error,而不是使用异常)的糟糕库。

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

评论

4赞 Guvante 9/25/2008
回复:Anheledir 实际上,在第一次调用后,我会为 null。您必须使用显式转换函数来获取字符串的值。
53赞 Guvante 9/25/2008
回复:Sander 实际上还有另一个很好的理由,它简化了您的检查代码(检查 null 而不是检查 null 和正确的类型)这很有帮助,因为很多时候您宁愿抛出自定义一个异常。但事实是,盲目打电话是不好的。
7赞 Calum 9/25/2008
#2 对于您不知道输入类型的 Equals 方法之类的事情很方便。不过,一般来说,是的,1 会是首选。尽管比这更受欢迎,但显然使用类型系统来限制一种类型,而您只需要一个:)
9赞 AnthonyWJones 9/25/2008
#2 也很有用,当你的代码可能会对特定类型执行某些特定操作,但除此之外什么都不做。
3赞 Aidiakapi 4/29/2015
作为一般经验法则。使用后必须进行测试,看看它是否是 。或者至少是接受作为有效输入的其他代码。也就是说,我经常使用强制转换,我使用它的一个例子是,该方法接受但可以预分配动态大小的集合,如果它也是一个 .asnullnullasIEnumerable<T>ICollectionICollection col = input as ICollection; if (col != null) something.Reserve(col.Count);
35赞 Blair Conrad 9/25/2008 #3

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

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

使用运算符,如果不是字符串,则设置为 ,如果您不确定并想测试,这将很方便:asosnulls

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

但是,如果不执行该测试,则稍后将使用并引发 NullReferenceException。这些往往更常见,一旦它们在野外发生就更难追踪,因为几乎每一行都取消了对变量的引用,并可能抛出一个变量。另一方面,如果你试图强制转换为值类型(任何基元,或结构,如 DateTime),你必须使用直接强制转换 - 这将不起作用。sas

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

评论

2赞 John Gibb 1/14/2014
一个注意事项 - 您可以使用可为 null 的值类型。即 不会起作用,但会......aso as DateTimeo as DateTime?
0赞 BornToCode 4/3/2014
为什么不改用呢?if (s is string)
1赞 Blair Conrad 4/3/2014
@BornToCode,对我来说,很大程度上是个人喜好。根据你正在做的事情,通常在ing之后,无论如何你都必须再次施法,所以你有是,然后是硬施法。出于某种原因,和 null 检查对我来说感觉更好。isas
1赞 Etherman 5/6/2022
现在我们可以使用: if (o is string s) { DoSomethingWithString(s); } else { ... }
5赞 Rob 9/25/2008 #4

“(string)o” 将导致 InvalidCastException,因为没有直接强制转换。

“o as string” 将导致 s 成为 null 引用,而不是抛出异常。

“o.ToString()”本身不是任何类型的强制转换,它是由对象实现的方法,因此以某种方式,由 .net 中的每个类实现,这些类对调用它的类的实例“执行某些操作”并返回一个字符串。

不要忘记,为了转换为字符串,还有 Convert.ToString(someType instanceOfThatType),其中 someType 是一组类型之一,本质上是框架基类型。

431赞 Quibblesome 9/25/2008 #5
  1. string s = (string)o;当某事绝对应该是另一件事时使用。
  2. string s = o as string;当某物可能是另一个时使用 东西。
  3. string s = o.ToString();当你不在乎什么时使用 是的,但你只想使用 可用的字符串表示形式。

评论

1赞 j riv 7/2/2017
我感觉这个答案听起来不错,但可能不准确。
5赞 Uxonith 9/14/2017
我喜欢前两个选项,但我会在第三个选项中添加“并且您确定它不是空的”。
3赞 Quibblesome 9/14/2017
这些天你可以使用 Elvis (?.) 来避免关心这个问题:obj?。ToString()
1赞 whytheq 5/26/2019
@Quibblesome很好的答案:如果我添加 1/2/3 是什么,这样就没有必要向上滚动到 OP,你会生气吗?我和 SO 会根据投票对旧答案进行排名!
6赞 Joel in Gö 9/25/2008 #6

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

假设 a 是动物:

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

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

以最少的石膏喂食。

评论

2赞 hiddensunset4 11/24/2012
@Chirs Moutray,这并不总是可能的,特别是如果它是一个图书馆。
10赞 Mark Cidade 9/25/2008 #7

如果您已经知道它可以强制转换为哪种类型,请使用 C 样式强制转换:

var o = (string) iKnowThisIsAString; 

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

如果您不知道它是否是所需的类型,如果是,您将使用它,请使用 as 关键字:

var s = o as string;
if (s != null) return s.Replace("_","-");

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

请注意,as 不会调用任何类型转换运算符。仅当对象不是 null 且本机为指定类型时,它才会为非 null。

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

评论

3赞 AnthonyWJones 9/25/2008
这是关于类型转换运算符的一个有趣的小问题。我为几种类型创建了转换,然后必须注意这一点。
4赞 Chris S 9/25/2008 #8
string s = o as string; // 2

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

评论

0赞 Matt 6/26/2015
嗨,克里斯,这个答案中的链接现在是 404......我不确定您是否有要放入的替代品?
8赞 Glenn Slaven 9/25/2008 #9

as 关键字在使用 FindControl 方法时 asp.net 很好。

Hyperlink link = this.FindControl("linkid") as Hyperlink;
if (link != null)
{
     ...
}

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

object linkObj = this.FindControl("linkid");
if (link != null)
{
     Hyperlink link = (Hyperlink)linkObj;
}

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

8赞 Brady Moritz 8/16/2010 #10

根据本页进行的实验:http://www.dotnetguru2.org/sebastienros/index.php/2006/02/24/cast_vs_as

(此页面有时会出现一些“非法引荐来源”错误,因此如果出现,请刷新)

结论是,“as”运算符通常比强制转换快。有时快很多倍,有时只是勉强快。

我个人认为“as”也更具可读性。

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

1赞 xtrem 6/23/2012 #11

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

String s = String.Concat(o);
4赞 BornToCode 4/3/2014 #12

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

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

你只能写:

((string)o).ToLower();

但你可以改写:

(o as string).ToLower();

该选项更具可读性(至少在我看来)。as

评论

0赞 james 8/31/2016
(o 作为字符串)。ToLower() 构造违背了 as 运算符的目的。当 o 无法转换为字符串时,这将引发 null 引用异常。
0赞 BornToCode 8/31/2016
@james - 但是谁说 as 运算符的唯一目的是在强制转换失败时抛出异常?如果你知道 o 是一个字符串,并且只想编写更干净的代码,你可以用它来代替多个令人困惑的括号。(o as string).ToLower()
0赞 james 9/2/2016
AS 的目的恰恰相反 - 当强制转换失败时,它不应该抛出异常,它应该返回 null。假设你的 o 是一个值为 null 的字符串,那么会发生什么?提示 - 您的 ToLower 调用将失败。
0赞 BornToCode 9/4/2016
@james - 你是对的,但是我确定它不会是空的,我只需要为编译器进行强制转换以让我访问该对象的方法呢?
1赞 james 9/5/2016
你绝对可以这样做,但这并不完全是最佳做法,因为你不想依赖调用方或外部系统来确保你的值不为 null。如果您使用的是 C#6,那么您可以执行(o 作为字符串)?。ToLower() 中。
0赞 Bennett Yeo 10/14/2015 #13

由于没有人提到它,因此通过关键字最接近 instanceOf 的 Java 是这样的:

obj.GetType().IsInstanceOfType(otherObj)

评论

0赞 Randall Flagg 11/19/2023
在您的代码中,obj 必须不为 null,并且不确定它是否存在。另外,我认为“是”,是最接近java的instanceof:learn.microsoft.com/en-us/dotnet/csharp/language-reference/...
6赞 Lucas Teixeira 9/25/2016 #14

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

直接铸造

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

  • 自定义隐式/显式强制转换:通常会创建一个新对象。
  • 隐式值类型:在不丢失信息的情况下复制。
  • 显式值类型:副本和信息可能会丢失。
  • 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
    }
3赞 Dmitry 8/3/2017 #15

如果在应用的逻辑上下文中是唯一有效的类型,则使用直接强制转换。通过这种方法,您将获得并实现快速失败的原则。您的逻辑将受到保护,不会进一步传递无效类型或获取 NullReferenceException(如果使用运算符)。string s = (string) o;stringInvalidCastExceptionas

如果逻辑需要几种不同类型的强制转换,请选中它或使用运算符。string s = o as string;nullis

C# 7.0 中出现了新的很酷的功能,以简化强制转换并检查模式匹配

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;
 }
4赞 V. S. 10/17/2018 #16

我想提请注意 as 运算符的以下细节:

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

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