【问题标题】:Casting vs using the 'as' keyword in the CLR在 CLR 中强制转换与使用“as”关键字
【发布时间】:2010-10-04 12:14:57
【问题描述】:

在编程接口时,我发现我做了很多转换或对象类型转换。

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

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

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

【问题讨论】:

  • 您能否添加一个小例子来说明您为什么首先在问题中使用演员表,或者也许开始一个新的?我对为什么你只需要单元测试的演员表感兴趣。我认为这超出了这个问题的范围。
  • 我可能可以更改我的单元测试以防止这种需要。基本上它归结为这样一个事实,即我的具体对象上有一个不在接口中的属性。我需要设置该属性,但现实生活中该属性将通过其他方式设置。这能回答你的问题吗?
  • 正如 Patrik Hägne 在下面敏锐地指出的那样,IS 有所不同。

标签: c# casting clr


【解决方案1】:

下面的答案写于 2008 年。

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

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

请注意,tt 在此之后仍在范围内,但未明确分配。 (它肯定是在if 正文中分配的。)在某些情况下这有点烦人,所以如果你真的关心在每个范围内引入尽可能少的变量,你可能仍然想使用is 后跟一个演员表。


到目前为止,我认为没有任何答案(在开始此答案时!)真正解释了值得在哪里使用哪个。

  • 不要这样做:

    // 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 可能是TargetType的一个实例并且TargetType是一个值类型,那么我们不能将as与TargetType本身一起使用,但我们可以使用可空类型:

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

    (注意:目前是actually slower than is + cast。我认为它更优雅和一致,但我们开始了。)

  • 1234563在这种情况下,TargetType 是引用类型还是值类型都没有关系。 1234563 /p>
  • 在此之前,我几乎可以肯定将 is 用于值类型的情况,但没想过同时使用可空类型和 as :)


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

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

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

  • 它们之间没有显着差异。 (事实上​​,在某些情况下 as-plus-null-check 肯定 is 较慢。上面的代码实际上使类型检查变得容易,因为它是针对密封类的;如果您正在检查一个界面,余额提示稍微支持 as-plus-null-check。)
  • 他们都疯狂快。这只是不会成为您代码中的瓶颈,除非您以后真的不打算对这些值做任何事情。

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

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

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

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

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

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

【讨论】:

  • 这里是使用 IL 的 is/as/casting 的成本:atalasoft.com/cs/blogs/stevehawley/archive/2009/01/30/…
  • 如果targetObject 可能是目标类型,为什么使用“is”和cast组合被认为是一种不好的做法?我的意思是,它生成的代码较慢,但在这种情况下,意图比 AS 转换更清晰,例如“如果 targetObject 为 targetType,则执行某些操作”,而不是“如果 targetObject 为空,则执行某些操作”,此外,AS 子句将创建一个不必要的变量超出 IF 范围。
  • @Valera:好点,虽然我建议 as/null 测试足够惯用,几乎所有 C# 开发人员都应该清楚其意图。我个人不喜欢 is + cast 中涉及的重复。我实际上想要一种“好像”的构造,它可以同时执行两种操作。他们经常在一起……
  • @Jon Skeet:抱歉我迟到了。Is And Cast:2135, Is And As:2145, As And null check: 1961,specs: OS:Windows Seven, CPU:i5-520M, 4GB DDR3 1033 内存,对 128,000,000 个项目的阵列进行基准测试。
  • 使用 C# 7,您可以:if (randomObject is TargetType convertedRandomObject){ // Do stuff with convertedRandomObject.Value} 或使用 switch/casesee docs
【解决方案2】:

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

casting before 会引发异常。

为了性能,引发异常通常会花费更多的时间。

【讨论】:

  • 引发异常的成本更高,但如果您知道可以正确转换对象,则 as 需要更多时间,因为需要进行安全检查(请参阅 Anton 的回复)。但是,我认为安全检查的成本非常小。
  • 可能引发异常的成本是一个需要考虑的因素,但它通常是正确的设计。
  • @panesofglass - 对于引用类型,将始终在运行时检查 as 和 cast 的转换兼容性,因此该因素不会区分这两个选项。 (如果不是这样,那么 cast 不能引发异常。)
  • @Frank - 例如,如果您需要使用预泛型集合,并且您的 API 中的方法需要员工列表,而某些小丑则传递了产品列表,那么无效转换异常可能适合表示违反接口要求。
  • @user29439 抱歉,但是用于“as”运算符的 IL OP 代码“isinst”比用于直接转换的 OP 代码“castclass”快。因此,对于引用类型,即使对象可以毫无例外地强制转换,“as”也会执行得更快。 Unity 引擎还使用 IL2CPP 为“as”生成更高性能的代码。
【解决方案3】:

这是另一个答案,有一些 IL 比较。考虑类:

public class MyClass
{
    public static void Main()
    {
        // Call the 2 methods
    }

    public void DirectCast(Object obj)
    {
        if ( obj is MyClass)
        { 
            MyClass myclass = (MyClass) obj; 
            Console.WriteLine(obj);
        } 
    } 


    public void UsesAs(object obj) 
    { 
        MyClass myclass = obj as MyClass; 
        if (myclass != null) 
        { 
            Console.WriteLine(obj);
        } 
    }
}

现在看看每种方法产生的 IL。即使操作码对您没有任何意义,您也可以看到一个主要区别 - 在 DirectCast 方法中调用 isinst 后跟 castclass。所以基本上是两个电话而不是一个。

.method public hidebysig instance void  DirectCast(object obj) cil managed
{
  // Code size       22 (0x16)
  .maxstack  8
  IL_0000:  ldarg.1
  IL_0001:  isinst     MyClass
  IL_0006:  brfalse.s  IL_0015
  IL_0008:  ldarg.1
  IL_0009:  castclass  MyClass
  IL_000e:  pop
  IL_000f:  ldarg.1
  IL_0010:  call       void [mscorlib]System.Console::WriteLine(object)
  IL_0015:  ret
} // end of method MyClass::DirectCast

.method public hidebysig instance void  UsesAs(object obj) cil managed
{
  // Code size       17 (0x11)
  .maxstack  1
  .locals init (class MyClass V_0)
  IL_0000:  ldarg.1
  IL_0001:  isinst     MyClass
  IL_0006:  stloc.0
  IL_0007:  ldloc.0
  IL_0008:  brfalse.s  IL_0010
  IL_000a:  ldarg.1
  IL_000b:  call       void [mscorlib]System.Console::WriteLine(object)
  IL_0010:  ret
} // end of method MyClass::UsesAs

isinst 关键字与 castclass 对比

This blog post 在这两种方法之间进行了不错的比较。他的总结是:

  • 在直接比较中,isinst 比 castclass 快(虽然只是稍微)
  • 当必须执行检查以确保转换成功时,isinst 比 castclass 快得多
  • 不应使用 isinst 和 castclass 的组合,因为这比最快的“安全”转换要慢得多(慢 12% 以上)

我个人总是使用 As,因为它易于阅读并且是 .NET 开发团队(或者 Jeffrey Richter)推荐的

【讨论】:

  • 我一直在寻找关于 cast 与 as 的明确解释,这个答案使其更加清晰,因为它涉及到常见的中间语言逐步解释。谢谢!
【解决方案4】:

两者之间更细微的区别之一是在涉及强制转换运算符时不能使用“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;
    }
}

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

【讨论】:

    【解决方案5】:
    如果

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

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

    另一方面,C 风格的转换在无法进行转换时抛出异常。

    【讨论】:

    • 等价,是的,但不一样。这会生成更多的代码。
    【解决方案6】:

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

    如果您正在对接口进行编程,则不需要强制转换。希望这些演员非常罕见。如果不是,您可能需要重新考虑您的一些界面。

    【讨论】:

    • 到目前为止,我的单元测试主要需要强制转换,但感谢您提出。在我处理这个问题时,我会牢记这一点。
    • 同意toad,我也很好奇为什么单元测试方面与你的铸造有关@Frank V。需要铸造的地方,往往需要重新设计或重构因为这表明您试图硬塞不同的问题,而这些问题应该以不同的方式进行管理。
    • @TheSenator 这个问题已经超过 3 年了,所以我不太记得了。但是即使在进行单元测试时,我也可能会积极地使用这些接口。可能是因为我使用的是工厂模式,并且无法访问要测试的目标对象上的公共构造函数。
    【解决方案7】:

    请忽略 Jon Skeet 的建议,重新:避免 test-and-cast 模式,即:

    if (randomObject is TargetType)
    {
        TargetType foo = randomObject as TargetType;
        // Do something with foo
    }
    

    这比强制转换和空测试花费更多的想法是神话:

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

    这是一个不起作用的微优化。我跑了some real tests,测试和转换实际上比转换和空比较快,而且它也更安全,因为你不可能在 if 应该转换的范围内有一个空引用失败。

    如果你想知道为什么 test-and-cast 更快,或者至少不慢,那么有一个简单而复杂的原因。

    简单:即使是简单的编译器也会将两个类似的操作(如测试和转换)合并为一个测试和分支。 cast-and-null-test 可能会强制执行两个测试和一个分支,一个用于类型测试并在失败时转换为 null,另一个用于 null 检查本身。至少,它们都会针对单个测试和分支进行优化,因此 test-and-cast 不会比 cast-and-null-test 慢也不会快。

    复杂性:为什么 test-and-cast 更快:cast-and-null-test 将另一个变量引入外部范围,编译器必须跟踪它的活性,它可能无法优化根据您的控制流的复杂程度来消除该变量。相反,test-and-cast 只在一个定界范围内引入一个新变量,因此编译器知道该变量在范围退出后是死的,因此可以更好地优化寄存器分配。

    所以,请让这个“cast-and-null-test is better than test-and-cast”的建议消亡。请。 test-and-cast 更安全、更快捷。

    【讨论】:

    • @naasking:如果您测试两次(根据您的第一个 sn-p),如果它是字段或 ref 参数,则类型可能会在两次测试之间发生变化。它对局部变量是安全的,但对字段不安全。我有兴趣运行您的基准测试,但您在博客文章中提供的代码并不完整。我同意不进行微优化,但我不认为使用两次值比使用“as”和无效测试更具可读性或优雅性。 (顺便说一句,我肯定会在 is 之后使用直接转换而不是“as”。)
    • 我也不明白为什么它更安全。实际上,我已经说明了为什么它less 安全。当然,你最终会得到一个范围内的变量,它可能为空,但除非你开始在后续“if”块的范围之外使用它,否则你很好。我提出的安全问题(围绕字段更改其值)是显示的代码的真正问题 - 您的安全问题要求开发人员在其他代码中松懈。
    • +1 用于指出 is/cast 或 as/cast 实际上并不慢,请注意。我自己运行了一个完整的测试后,我可以确认它没有任何区别 - 坦率地说,您可以在很短的时间内运行 令人难以置信的 数量的演员表。将使用完整代码更新我的答案。
    • 确实,如果绑定不是本地绑定,则可能会出现 TOCTTOU 错误(检查时间到使用时间),这点很好。至于为什么它更安全,我和很多出于某种原因喜欢重用本地人的初级开发人员一起工作。因此,根据我的经验,cast-and-null 是非常现实的危险,而且我从来没有遇到过 TOCTTOU 的情况,因为我没有那样设计我的代码。至于运行时测试速度,甚至比 virtual dispatch [1] 还要快!回复:代码,我看看能不能找到演员表测试的源代码。 [1]higherlogics.blogspot.com/2008/10/…
    • @naasking:我从来没有遇到过本地重用问题——但我想说它在代码审查中比更微妙的 TOCTTOU 错误更容易发现。还值得指出的是,我刚刚对接口而不是密封类重新运行了我自己的基准检查,这提示性能有利于 as-then-null-check... 但正如我所说,性能不是这不是我在这里选择任何特定方法的原因。
    【解决方案8】:

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

    【讨论】:

    • 值类型没有默认值。 As 不能用于转换值类型。
    • 'as' 关键字实际上对值类型不起作用,所以它总是设置为 null。
    【解决方案9】:

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

    通常你不应该从例如投射一个对象。 IMyInterface 到 MyClass。接口的好处在于,如果你将一个对象作为实现接口的输入,那么你就不必关心你得到的是什么类型的对象。

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

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

    【讨论】:

      【解决方案10】:

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

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

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

      【讨论】:

      • 'as' 也可以用于可空值类型,这提供了一种有趣的模式。请参阅我的代码答案。
      【解决方案11】:

      我的答案只是在我们不检查类型并且在转换后不检查空值的情况下的速度。我在 Jon Skeet 的代码中添加了两个额外的测试:

      using System;
      using System.Diagnostics;
      
      class Test
      {
          const int Size = 30000000;
      
          static void Main()
          {
              object[] values = new object[Size];
      
              for (int i = 0; i < Size; i++)
              {
                  values[i] = "x";
              }
              FindLengthWithIsAndCast(values);
              FindLengthWithIsAndAs(values);
              FindLengthWithAsAndNullCheck(values);
      
              FindLengthWithCast(values);
              FindLengthWithAs(values);
      
              Console.ReadLine();
          }
      
          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);
          }
          static void FindLengthWithCast(object[] values)
          {
              Stopwatch sw = Stopwatch.StartNew();
              int len = 0;
              foreach (object o in values)
              {
                  string a = (string)o;
                  len += a.Length;
              }
              sw.Stop();
              Console.WriteLine("Cast: {0} : {1}", len,
                                (long)sw.ElapsedMilliseconds);
          }
      
          static void FindLengthWithAs(object[] values)
          {
              Stopwatch sw = Stopwatch.StartNew();
              int len = 0;
              foreach (object o in values)
              {
                  string a = o as string;
                  len += a.Length;
              }
              sw.Stop();
              Console.WriteLine("As: {0} : {1}", len,
                                (long)sw.ElapsedMilliseconds);
          }
      }
      

      结果:

      Is and Cast: 30000000 : 88
      Is and As: 30000000 : 93
      As and null check: 30000000 : 56
      Cast: 30000000 : 66
      As: 30000000 : 46
      

      不要试图专注于速度(就像我一样),因为这一切都非常非常快。

      【讨论】:

      • 同样,在我的测试中,我发现as 转换(没有错误检查)的运行速度比强制转换快大约 1-3%(大约 540 毫秒,而 1 亿次迭代为 550 毫秒)。两者都不会成就或破坏您的应用程序。
      【解决方案12】:

      除了这里已经公开的所有内容之外,我刚刚发现了一个我认为值得注意的实际差异,显式转换之间

      var x = (T) ...
      

      相对于使用as 运算符。

      示例如下:

      class Program
      {
          static void Main(string[] args)
          {
              Console.WriteLine(GenericCaster<string>(12345));
              Console.WriteLine(GenericCaster<object>(new { a = 100, b = "string" }) ?? "null");
              Console.WriteLine(GenericCaster<double>(20.4));
      
              //prints:
              //12345
              //null
              //20.4
      
              Console.WriteLine(GenericCaster2<string>(12345));
              Console.WriteLine(GenericCaster2<object>(new { a = 100, b = "string" }) ?? "null");
      
              //will not compile -> 20.4 does not comply due to the type constraint "T : class"
              //Console.WriteLine(GenericCaster2<double>(20.4));
          }
      
          static T GenericCaster<T>(object value, T defaultValue = default(T))
          {
              T castedValue;
              try
              {
                  castedValue = (T) Convert.ChangeType(value, typeof(T));
              }
              catch (Exception)
              {
                  castedValue = defaultValue;
              }
      
              return castedValue;
          }
      
          static T GenericCaster2<T>(object value, T defaultValue = default(T)) where T : class
          {
              T castedValue;
              try
              {
                  castedValue = Convert.ChangeType(value, typeof(T)) as T;
              }
              catch (Exception)
              {
                  castedValue = defaultValue;
              }
      
              return castedValue;
          }
      }
      

      底线: GenericCaster2 不适用于结构类型。 GenericCaster 会。

      【讨论】:

        【解决方案13】:

        如果您使用面向 .NET Framework 4.X 的 Office PIA,则应使用 as 关键字,否则无法编译。

        Microsoft.Office.Interop.Outlook.Application o = new Microsoft.Office.Interop.Outlook.Application();
        Microsoft.Office.Interop.Outlook.MailItem m = o.CreateItem(Microsoft.Office.Interop.Outlook.OlItemType.olMailItem) as Microsoft.Office.Interop.Outlook.MailItem;
        

        Casting 在面向 .NET 2.0 时是可以的:

        Microsoft.Office.Interop.Outlook.MailItem m = (Microsoft.Office.Interop.Outlook.MailItem)o.CreateItem(Microsoft.Office.Interop.Outlook.OlItemType.olMailItem);
        

        当面向 .NET 4.X 时,错误是:

        错误 CS0656:缺少编译器所需的成员“Microsoft.CSharp.RuntimeBinder.Binder.Convert”

        错误 CS0656:缺少编译器所需的成员“Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo.Create”

        【讨论】:

          【解决方案14】:

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

          【讨论】:

          • 不一样,在IL代码中一个调用CastClass,另一个调用IsInst。
          【解决方案15】:

          您选择什么很大程度上取决于需要什么。 我更喜欢显式转换

          IMyInterface = (IMyInterface)someobj;
          

          因为如果对象应该是 IMyInterface 类型而不是 - 这绝对是个问题。 最好尽早发现错误,因为准确的错误会被修复而不是修复它的副作用。

          但是,如果您处理接受object 作为参数的方法,那么您需要在执行任何代码之前检查其确切类型。在这种情况下,as 会很有用,因此您可以避免使用InvalidCastException。

          【讨论】:

            【解决方案16】:

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

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

            【讨论】:

              【解决方案17】:
              【解决方案18】:

              OP 的问题仅限于特定的施法情况。标题涵盖了更多情况。
              以下是我目前能想到的所有相关铸造情况的概述:

              private class CBase
              {
              }
              
              private class CInherited : CBase
              {
              }
              
              private enum EnumTest
              {
                zero,
                one,
                two
              }
              
              private static void Main (string[] args)
              {
                //########## classes ##########
                // object creation, implicit cast to object
                object oBase = new CBase ();
                object oInherited = new CInherited ();
              
                CBase oBase2 = null;
                CInherited oInherited2 = null;
                bool bCanCast = false;
              
                // explicit cast using "()"
                oBase2 = (CBase)oBase;    // works
                oBase2 = (CBase)oInherited;    // works
                //oInherited2 = (CInherited)oBase;   System.InvalidCastException
                oInherited2 = (CInherited)oInherited;    // works
              
                // explicit cast using "as"
                oBase2 = oBase as CBase;
                oBase2 = oInherited as CBase;
                oInherited2 = oBase as CInherited;  // returns null, equals C++/CLI "dynamic_cast"
                oInherited2 = oInherited as CInherited;
              
                // testing with Type.IsAssignableFrom(), results (of course) equal the results of the cast operations
                bCanCast = typeof (CBase).IsAssignableFrom (oBase.GetType ());    // true
                bCanCast = typeof (CBase).IsAssignableFrom (oInherited.GetType ());    // true
                bCanCast = typeof (CInherited).IsAssignableFrom (oBase.GetType ());    // false
                bCanCast = typeof (CInherited).IsAssignableFrom (oInherited.GetType ());    // true
              
                //########## value types ##########
                int iValue = 2;
                double dValue = 1.1;
                EnumTest enValue = EnumTest.two;
              
                // implicit cast, explicit cast using "()"
                int iValue2 = iValue;   // no cast
                double dValue2 = iValue;  // implicit conversion
                EnumTest enValue2 = (EnumTest)iValue;  // conversion by explicit cast. underlying type of EnumTest is int, but explicit cast needed (error CS0266: Cannot implicitly convert type 'int' to 'test01.Program.EnumTest')
              
                iValue2 = (int)dValue;   // conversion by explicit cast. implicit cast not possible (error CS0266: Cannot implicitly convert type 'double' to 'int')
                dValue2 = dValue;
                enValue2 = (EnumTest)dValue;  // underlying type is int, so "1.1" beomces "1" and then "one"
              
                iValue2 = (int)enValue;
                dValue2 = (double)enValue;
                enValue2 = enValue;   // no cast
              
                // explicit cast using "as"
                // iValue2 = iValue as int;   error CS0077: The as operator must be used with a reference type or nullable type
              }
              

              【讨论】:

                猜你喜欢
                • 2014-03-07
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2011-01-22
                • 1970-01-01
                相关资源
                最近更新 更多