强制转换与在 CLR 中使用“as”关键字

Casting vs using the 'as' keyword in the CLR

提问人:Frank V 提问时间:1/31/2009 最后编辑:Frank V 更新时间:8/14/2017 访问量:87659

问:

在对接口进行编程时,我发现我正在做大量的转换或对象类型转换。

这两种转换方法之间有区别吗?如果是这样,是否存在成本差异,或者这对我的计划有何影响?

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

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

C# 强制转换 CLR

评论

0赞 Erik van Brakel 1/31/2009
您能否在问题中添加一个小例子,说明您首先为什么要使用演员表,或者开始一个新的例子?我对为什么您只需要将演员表用于单元测试有点感兴趣。我认为这超出了这个问题的范围。
2赞 Frank V 1/31/2009
我可能会更改我的单元测试以防止这种需求。基本上,它归结为这样一个事实,即我在我的具体对象上有一个不在界面中的属性。我需要设置该属性,但现实生活中该属性将通过其他方式设置。这回答了你的问题吗?
0赞 Neil 1/31/2009
正如帕特里克·哈涅(Patrik Hägne)在下面敏锐地指出的那样,这是区别的。

答:

79赞 Patrick Desjardins 1/31/2009 #1

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

cast before 将引发异常。

对于性能而言,引发异常通常在时间上成本更高。

评论

4赞 1/31/2009
引发异常的成本更高,但如果您知道可以正确转换对象,因为安全检查需要更多时间(请参阅 Anton 的响应)。但是,我认为安全检查的成本非常小。
17赞 Jeffrey L Whitledge 1/31/2009
可能引发异常的成本是一个需要考虑的因素,但它通常是正确的设计。
0赞 Jeffrey L Whitledge 1/31/2009
@panesofglass - 对于引用类型,将始终在运行时检查 as 和 cast 的转换兼容性,因此该因子不会区分这两个选项。(如果不是这样,则 cast 无法引发异常。
4赞 Jeffrey L Whitledge 1/31/2009
@Frank - 例如,如果您需要使用预泛型集合,并且 API 中的某个方法需要 Employees 列表,而某些 joker 则传递了 Products 列表,则无效强制转换异常可能适合用于指示违反接口要求。
1赞 dmitry1100 6/19/2021
@user29439抱歉,用于“as”运算符的 IL OP 代码“isinst”比用于直接转换的 OP 代码“castclass”快。因此,对于引用类型,“as”的执行速度会更快,即使对象可以无一例外地被强制转换。Unity 引擎还使用 IL2CPP 为“as”生成性能更高的代码。
4赞 TheSmurf 1/31/2009 #2

如果强制转换失败,则“as”关键字不会引发异常;它将变量设置为 null(或值类型的默认值)。

评论

3赞 Patrik Hägne 1/31/2009
值类型没有默认值。不能用于强制转换值类型。
2赞 Erik van Brakel 1/31/2009
“as”关键字实际上不适用于值类型,因此它始终设置为 null。
13赞 Anton Gogolev 1/31/2009 #3

如果 AS 无法执行返回 NULL 的转换,则从不引发异常(AS 仅对引用类型进行操作)。所以使用 as 基本上等同于

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

另一方面,C 样式强制转换在无法转换时会引发异常。

评论

4赞 plinth 1/31/2009
等效,是的,但不一样。这会生成比 as 更多的代码。
0赞 Cerebrus 1/31/2009 #4

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

评论

0赞 Jenix 6/29/2016
不同,因为一个调用 CastClass,另一个调用 IsInst,在 IL 代码中。
10赞 toad 1/31/2009 #5

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

如果要对接口进行编程,则不需要强制转换。希望这些演员阵容非常罕见。如果没有,您可能需要重新考虑某些界面。

评论

0赞 Frank V 1/31/2009
到目前为止,我的单元测试主要需要铸造,但感谢您提出它。在我做这件事的时候,我会牢记这一点。
0赞 The Senator 3/4/2014
同意蟾蜍的观点,我也很好奇为什么单元测试方面与你的选角有关,@Frank V。在需要强制转换的地方,通常需要重新设计或重构,因为这表明你试图将不同的问题硬塞进去,而这些问题应该以不同的方式进行管理。
0赞 Frank V 3/5/2014
@TheSenator 这个问题已经有 3 年多了,所以我真的不记得了。但是即使在单元测试时,我也可能积极地使用这些接口。可能是因为我使用的是工厂模式,并且无法访问要测试的目标对象上的公共构造函数。
3赞 Jeffrey L Whitledge 1/31/2009 #6

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

强制转换可用于任何兼容类型,它可以重载,如果操作失败,它将引发异常。

选择使用哪种取决于具体情况。首先,这是您是否要在失败的转换上引发异常的问题。

评论

1赞 Jon Skeet 1/31/2009
“as”也可用于可为 null 的值类型,这提供了一个有趣的模式。请参阅我的代码答案。
0赞 Darryl Braaten 1/31/2009 #7

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

我的经验法则是,如果我总是希望变量是我想要的类型,那么我就使用强制转换。如果变量可能无法转换为我想要的内容,并且我准备处理使用 as 的 null,我将使用 as。

548赞 Jon Skeet 1/31/2009 #8

这句话下面的答案写于2008年。

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

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

请注意,在此之后仍在范围内,但尚未明确分配。(它肯定是在体内分配的。在某些情况下,这有点烦人,所以如果你真的关心在每个作用域中引入尽可能少数量的变量,你可能仍然想使用后跟一个强制转换。ttifis


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

  • 别这样:

    // Bad code - checks type twice for no reason
    if (randomObject is TargetType)
    {
        TargetType foo = (TargetType) randomObject;
        // Do something with foo
    }
    

    这不仅是两次检查,而且如果是字段而不是局部变量,它可能会检查不同的东西。如果另一个线程更改了两者之间的值,则“if”可能会通过,但强制转换会失败。randomObjectrandomObject

  • 如果真的应该是 的实例,即如果不是,则意味着存在错误,那么强制转换是正确的解决方案。这会立即引发异常,这意味着在不正确的假设下不会再做任何工作,并且异常会正确显示 bug 的类型。randomObjectTargetType

    // 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;
    
  • 如果可能是 的实例 和 是引用类型,则使用如下代码:randomObjectTargetTypeTargetType

    TargetType convertedRandomObject = randomObject as TargetType;
    if (convertedRandomObject != null)
    {
        // Do stuff with convertedRandomObject
    }
    
  • 如果可能是 和 的实例,则不能与自身一起使用,但我们可以使用可为 null 的类型:randomObjectTargetTypeTargetTypeasTargetType

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

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

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

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

  • 我几乎可以肯定之前已经使用过值类型情况,没有想过使用可为 null 的类型并一起:)isas


编辑:请注意,除了值类型情况外,以上内容都不涉及性能,我注意到将装箱为可空的值类型实际上较慢 - 但始终如一。

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

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-检查肯定慢。上面的代码实际上使类型检查变得容易,因为它是针对密封类的;如果要检查接口,则天平会略微倾向于 as-plus-null-check。
  • 它们的速度都非常快。这根本不会成为代码中的瓶颈,除非您之后真的不打算对这些值执行任何操作。

因此,我们不必担心性能。让我们担心正确性和一致性。

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

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

换句话说,有人写:

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

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

评论

10赞 plinth 1/31/2009
以下是 is/as/casting 以 IL 表示的成本:atalasoft.com/cs/blogs/stevehawley/archive/2009/01/30/...
3赞 Valera Kolupaev 5/28/2010
如果 targetObject 可能是目标类型,为什么使用“is”和强制转换组合被认为是一种不好的做法?我的意思是,它生成的代码速度较慢,但在这种情况下,意图比 AS 强制转换更清晰,例如“如果 targetObject 为 targetType,则执行某项操作”,而不是“如果 targetObject 为 null,则执行某项操作”,此外 AS 子句会在 IF 范围之外创建一个不必要的变量。
2赞 Jon Skeet 5/28/2010
@Valera:很好,尽管我认为 as/null 测试足够惯用,几乎所有 C# 开发人员都应该清楚其意图。就我个人而言,我不喜欢 is + 演员表中涉及的重复。我实际上想要一种“假设”结构,它将两个动作合二为一。他们经常在一起......
2赞 Behrooz 6/25/2010
@Jon Skeet:对不起,我迟到了。Is And Cast:2135, Is And As:2145, As And null 检查: 1961,规格: 操作系统:Windows Seven, CPU:i5-520M, 4GB DDR3 1033 ram, 基准测试阵列为 128,000,000 项。
2赞 WerWet 8/14/2017
使用 C# 7,您可以执行以下操作: 或使用/查看文档if (randomObject is TargetType convertedRandomObject){ // Do stuff with convertedRandomObject.Value}switchcase
4赞 f3lix 1/31/2009 #9

这不是对问题的回答,而是对问题代码示例的注释:

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

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

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

19赞 Patrik Hägne 1/31/2009 #10

两者之间更微妙的区别之一是,当涉及强制转换运算符时,“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;

1赞 Oleg 1/31/2009 #11

你选择什么很大程度上取决于需要什么。 我更喜欢显式铸造

IMyInterface = (IMyInterface)someobj;

因为如果对象应该是 IMyInterface 类型,而它不是 - 这肯定是问题。 最好尽早获得错误,因为精确的错误将被修复,而不是修复其副作用。

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

32赞 Chris S 1/31/2009 #12

这是另一个答案,有一些 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

这篇博文对这两种方法进行了很好的比较。他的总结是:

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

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

评论

0赞 Morse 8/17/2018
我一直在寻找对 casting vs as 的明确解释,这个答案使它更加清晰,因为它涉及常见的中间语言分步解释。谢谢!
8赞 naasking 11/14/2009 #13

请忽略 Jon Skeet 的建议,重新:避免测试和投射模式,即:

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
}

这是一种行不通的微优化。我运行了一些实际测试,测试和强制转换实际上比强制转换和 null 比较更快,而且它也更安全,因为如果强制转换失败,您不可能在 if 之外的范围内有 null 引用。

如果你想要一个测试和投射速度更快,或者至少不慢的原因,有一个简单而复杂的原因。

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

复杂:为什么测试和强制转换速度更快:强制转换和空测试将另一个变量引入外部范围,编译器必须跟踪该变量的活跃度,并且它可能无法根据控制流的复杂程度优化该变量。相反,test-and-cast 仅在分隔作用域中引入新变量,因此编译器知道该变量在作用域退出后已失效,因此可以更好地优化寄存器分配。

所以,请让这个“铸造和空测试比测试和铸造更好”的建议 DIE。请。测试和投射既安全又快捷。

评论

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

请看一下这些链接:

它们向您展示了一些详细信息和性能测试。

2赞 CoperNick 9/26/2013 #15

我的答案只是关于速度,如果我们不检查类型,并且在强制转换后不检查空值。我在 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

不要试图专注于速度(就像我所做的那样),因为所有这些都非常非常快。

评论

0赞 palswim 2/12/2016
同样,在我的测试中,我发现转换(没有错误检查)比强制转换快 1-3%(大约 540 毫秒,而 1 亿次迭代的 550 毫秒)。两者都不会成就或破坏您的应用程序。as
1赞 Veverke 7/20/2015 #16

除了这里已经暴露的所有内容之外,我刚刚遇到了一个我认为值得注意的实际差异,即明确的选角

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 将。

2赞 anon 12/8/2015 #17

如果使用面向 .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;

不过,面向 .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”

0赞 Tobias Knauss 7/3/2017 #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
}