C#中用于类型转型的最佳实践是哪种?
这个问题在这里已经有了答案:
至少有两种铸造可能性和一种用于类型检查。 每个人都有自己的目的,这取决于具体情况:
硬铸
var myObject = (MyType)source;
如果您完全确定给定的对象是否属于该类型,通常会这样做。 如果您订阅了事件处理程序并将发件人对象转换为正确的类型来处理它,则可以使用它。
private void OnButtonClick(object sender, EventArgs e)
{
var button = (Button)sender;
button.Text = "Disabled";
button.Enabled = false;
}
软铸
var myObject = source as MyType;
if (myObject != null)
// Do Something
如果你不知道你是否真的有这种类型,通常会使用它。 所以只需简单地将它投射出去,如果不可能的话,简单地给一个null。 一个常见的例子是,如果您只有在满足某个界面的情况下才需要执行某些操作:
var disposable = source as IDisposable;
if(disposable != null)
disposable.Dispose();
类型检查
var isMyType = source is MyType;
这很少正确使用。 这种类型检查仅在您需要知道某个特定类型的东西时才有用,但您不必使用该对象。
if(source is MyType)
DoSomething();
else
DoSomethingElse();
我认为这是一个很好的问题,值得认真和详细的答案。 类型转换是C#实际上有很多不同的东西。
与C#不同,像C ++这样的语言对这些非常严格,所以我将使用那里的命名作为参考。 我总是认为最好理解事情是如何运作的,所以我会在这里详细介绍它。 开始:
动态转换和静态转换
C#具有值类型和引用类型。 引用类型始终遵循继承链,从Object开始。
基本上,如果你这样做(Foo)myObject
,你实际上正在做一个动态转换,如果你正在做(object)myFoo
(或者简单地object o = myFoo
),你正在做一个静态转换。
动态转换需要您进行类型检查,也就是说,运行时将检查您正在投射的对象是否属于该类型。 毕竟,你正在抛弃继承树,所以你不妨完全抛弃其他东西。 如果是这种情况,最终会出现InvalidCastException
。 因此,动态转换需要运行时类型信息(例如,它要求运行时知道哪个对象具有什么类型)。
静态转换不需要类型检查。 在这种情况下,我们正在继承树中进行投射,所以我们已经知道类型转换会成功。 没有例外会被抛出,永远。
值类型转换是一种特殊类型的转换,它将不同的值类型(f.ex.从float转换为int)转换。 我将在稍后讨论。
因为,是演员
在IL中,唯一受支持的是castclass
(cast)和isinst
(as)。 该is
经营者为实现as
一个空检查,无非是对他们两人的结合方便的速记符号更多。 在C#中,你可以写is
因为: (myObject as MyFoo) != null
。
as
检查对象是否是特定类型一样,如果不是则返回null。 对于静态转换的情况,我们可以确定这个编译时间,对于动态转换的情况,我们必须在运行时检查这个情况。
(...)
再次检查类型是否正确,如果不是,则抛出异常。 它基本上和as
相同,但是用throw而不是null
结果。 这可能会让你想知道为什么as
不能作为异常处理程序实现 - 这可能是因为异常相对较慢。
拳击
当您在特型演员的发生box
的值类型为对象。 基本上发生的事情是,.NET运行时将你的值类型复制到堆上(有一些类型信息),并将地址作为引用类型返回。 换句话说:它将值类型转换为引用类型。
当你有这样的代码时会发生这种情况:
int n = 5;
object o = n; // boxes n
int m = (int)o; // unboxes o
拆箱需要您指定类型。 在拆箱操作期间,类型被检查(类似于动态转换的情况,但它更简单,因为值类型的继承链很简单),并且如果类型匹配,则将值复制回堆栈。
你可能期望值类型转换为隐式的 - 因为上面他们不是。 允许的唯一拆箱操作是对精确值类型进行拆箱。 换一种说法:
sbyte m2 = (sbyte)o; // throws an error
值类型转换
如果你将一个float
为一个int
,那么你基本上是在转换这个值。 对于基本类型(IntPtr,(u)int 8/ conv_*
,float,double),这些转换在IL中预先定义为conv_*
指令,这些指令等同于位转换(int8 - > int16),截断(int16 - > int8)和转换(float - > int32)。
这里有一些有趣的事情发生在这里。 运行时似乎可以处理堆栈中的大量32位值,因此即使在您不希望它们的地方也需要进行转换。 例如,考虑:
sbyte sum = (sbyte)(sbyte1 + sbyte2); // requires a cast. Return type is int32!
int sum = int1 + int2; // no cast required, return type is int32.
标志扩展可能会比较棘手,可能会让你头晕目眩。 计算机将有符号的整数值存储为1补码。 以十六进制表示法int8,这意味着值-1是0xFF。 那么如果我们将它转换为int32,会发生什么? 同样,-1的补码值为0xFFFFFFFF - 所以我们需要将最重要的位传播到其余的'增加'位。 如果我们正在做无符号扩展,我们需要传播零。
为了说明这一点,下面是一个简单的测试用例:
byte b1 = 0xFF;
sbyte b2 = (sbyte)b1;
Console.WriteLine((int)b1);
Console.WriteLine((int)b2);
Console.ReadLine();
第一次强制转换为int在这里是零扩展,第二次强制转换为int是符号扩展。 您也可能想要使用“x8”格式的字符串来获取十六进制输出。
对于位转换,截断和转换之间的确切区别,我参考LLVM文档来解释差异。 查找sext
/ zext
/ bitcast
/ fptosi
和所有变体。
隐式类型转换
还有一个类别,那就是转换操作员。 MSDN详细介绍了如何重载转换运算符。 基本上你可以做的是通过重载一个操作符来实现你自己的转换。 如果您希望用户明确指定您打算投射,请添加explicit
关键字; 如果您希望隐式转换自动发生,您可以添加implicit
式转换。 基本上你会得到:
public static implicit operator byte(Digit d) // implicit digit to byte conversion operator
{
return d.value; // implicit conversion
}
...之后你可以做类似的东西
Digit d = new Digit(123);
byte b = d;
最佳实践
首先,了解差异,这意味着要实施小型测试程序,直到您理解上述所有内容之间的区别为止。 没有理解如何工作的代理人。
然后,我会坚持这些做法:
使用第二种方法,如果转换失败,则引发异常。
使用as
投射时,只能使用参考类型。 所以如果你将类型转换为值类型,你仍然必须使用int e = (int) o;
方法。
一个好的经验法则是:如果您可以将null指定为对象的值,则可以使用as
键入cast。
也就是说,null比较比抛出和捕获异常要快,所以在大多数情况下,使用as
应该会更快。
我不能诚实地说,这是否适用于您is
检查。 在另一个线程更改要投射的对象的某些多线程条件下,它可能会失败。
上一篇: Which is the best practice in C# for type casting?
下一篇: What is difference between normal typecasting and using “AS” keyword