65.9K
CodeProject 正在变化。 阅读更多。
Home

初学者教程 - C# 中的类型转换和类型转换

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.77/5 (33投票s)

2012 年 8 月 27 日

CPOL

6分钟阅读

viewsIcon

145247

downloadIcon

611

这篇简短的文章讨论了 C# 中的类型转换。

引言

在这篇讨论 C# 中类型转换的短文中,我们将探讨 隐式 类型转换是如何工作的,何时我们需要 显式 转换,以及如何增强我们用户定义类型以支持 隐式显式 转换。

背景

用任何语言编程都会涉及到内存数据的操作。访问这些内存数据是通过变量实现的。我们可以创建特定类型的变量来访问/操作某些数据。C# 允许我们创建多种类型的变量,但由于它是一种静态类型语言,它不允许我们将一种类型的变量值赋给另一种类型的变量。

从编译器的角度来看,将一种类型的变量值赋给另一种类型可能不是一个有效操作(隐式转换除外)。这也有道理,因为实际数据的表示因类型而异。但作为开发者,我们知道并可以理解数据类型之间的逻辑关系和转换。在很多情况下,避免这种从一种数据类型到另一种数据类型的赋值几乎是不可避免的。

出于上述原因,C# 提供了将一种数据类型转换为另一种数据类型的可能性。不仅如此,它还为我们提供了完全掌控类型转换的灵活性(用户定义转换)。此外,它还在库中内置了许多辅助类,提供了大多数常用的转换操作。

Using the Code

让我们来看看 C# 中可能的各种类型转换/转换方式。 

隐式转换

让我们先来看看 C# 自动进行的转换。隐式转换是不需要程序员进行显式转换的转换。一种数据类型可以直接赋给另一种。有一些规则支配着隐式转换。

  1. 内置数值类型,宽度转换
  2. 引用类型,派生类到基类

内置数值类型可以相互赋值,前提是窄类型被赋值给宽类型。这是可能的,因为编译器知道此类操作唯一的问题是需要更多的内存来存储该类型,而不会丢失或截断任何数据。所以以下转换不需要显式转换。 

int i = 10;
long l = i;

其次,如果我们尝试将派生类的值赋给基类,这是可行的。这是因为派生类总是“是一个”基类。此外,从内存角度来看,指向派生类对象的基类变量可以安全地访问内存中对象的基类部分,而不会出现任何问题。所以,以下代码无需显式转换即可工作。

class Base
{

}

class Derived : Base
{

}   

class Program
{
    static void Main(string[] args)
    {
        Derived d = new Derived();
        Base b = d;      
    }
}

除了这两种可能的情况外,所有转换都会导致编译时错误。尽管如此,如果我们需要执行转换,我们就必须使用显式类型转换/转换。

显式转换

如果我们发现自己需要进行窄化转换或不相关类型之间的转换,那么我们就必须使用显式转换。通过使用显式转换,我们实际上是告诉编译器,我们知道可能存在信息丢失,但我们仍然需要进行此转换。所以,如果我们想将 long 类型转换为 int 类型,我们就需要显式地转换它。

long l = 10;
int i = (int)l;

同样,如果我们想将基类转换为派生类,我们也必须显式地转换它。

Base b = new Base();
Derived d = (Derived)b;

显式转换实际上告诉编译器,我们知道可能存在信息丢失/不匹配,但我们仍然需要执行此转换。对于内置数值类型来说这没问题,但对于引用类型,可能存在类型根本不兼容的情况,即从一种类型到另一种类型的转换根本不可能。例如,将字符串 "abc" 转换为整数是不可能的。

此类转换表达式将成功编译,但会在运行时失败。C# 编译器会检查这两种类型是否兼容,如果不兼容,则会引发 InvalidCastException 异常。

'is' 和 'as' 运算符

因此,每当我们使用显式转换时,最好将转换包装在 try-catch 块中。C# 还提供了 isas 运算符,它们有助于以异常安全的方式执行显式转换。 

is 运算符检查被转换的类型是否与目标类型兼容,并返回一个 boolean 值。因此,使用 is 运算符执行显式转换的异常安全方法是: 

static void Main(string[] args)
{   
    // CASE 1 *****
    // This will work fine as o1 is actually an int
    object o1 = 1;
    int i = (int)o1;

    // CASE 2 *****
    // This won't work because o2 is not an int, so we need 
    // to have an is operator before the actual cast
    object o2 = "1";
    int j;

    if (o2 is int)
    {
        j = (int)o2;
    }

    // CASE 3 *****
    // We can never know what is the actual type of 
    // an object at runtime, so it's always better to use 
    // is operator, rewriting the first case
    object o3 = 1;
    int k;

    if (o3 is int)
    {
        k = (int)o3;
    }
}

上述代码片段中的情况 1 将引发异常,is o1 被赋值给某种不是 int 的类型,而其他两种情况是异常安全的,只有在类型兼容时才会执行转换。

使用 is 运算符存在一个小性能问题。上述代码片段中的情况 3 可以正常工作,但它会访问对象两次。一次用于检查兼容性,即 is 运算符,第二次用于实际提取值,即转换。我们不能有一个操作,“检查兼容性,如果兼容,则执行转换”吗?这时 as 运算符就派上用场了。

as 运算符会检查兼容性,如果兼容,它也会执行转换。如果转换不兼容或不成功,结果将是 null。因此,上面的转换可以使用 as 运算符重写

object o3 = 1;
int? k = o3 as int?;

if (k != null)
{
    //use k for whatever we want to
    Console.WriteLine(k.Value);
}

注意:这里需要注意的重要一点是,as 运算符只能用于引用类型。这就是我们在上面的示例中使用了 可空 int 的原因。

因此,如果我们希望进行异常安全的类型转换,我们可以使用 isas 运算符。如果目标类型是值类型,我们应该使用 is 运算符;如果目标类型是引用类型,我们应该使用 as 运算符。

用户定义转换

C# 还提供了在类和结构上定义转换的灵活性,以便它们可以与其他类型进行转换。转换运算符只是包含转换如何发生的逻辑。我们可以将这些转换运算符定义为隐式或显式。如果我们将其定义为隐式,转换将不需要显式转换即可发生。如果我们将其定义为显式,则需要进行类型转换。

让我们尝试看看如何实现这些转换操作。让我们实现一个小的 Rational 类来保存有理数。然后我们将定义两个转换操作。IntRational(隐式转换)和 Rationaldouble(显式转换)。

class Rational
{
    int numerator;
    int denominator;

    public Rational(int num, int den)
    {
        numerator = num;
        denominator = den;
    }

    public static implicit operator Rational(int i)
    {
        // since the rational equivalent of an int has 1 as denominator
        Rational rational = new Rational(i, 1);

        return rational;
    }

    public static explicit operator double(Rational r)
    {
        double result = ((double)r.numerator) / r.denominator;
        return result;
    }
}

现在,让我们看看如何使用这些转换运算符来执行实际的转换

static void Main(string[] args)
{   
    // Conversion from int to rational is implicit
    Rational r1 = 23;

    // Conversion from rational to double is explicit
    Rational r2 = new Rational(3, 2);
    double d = (double)r2;
}

在结束之前,还有一件事是初学者应该知道的。C# 中有许多辅助类和辅助函数可用于执行常用的转换。在编写转换代码之前,查阅文档以实现所需的转换始终是一个好主意。下面的代码片段演示了如何使用 Convert 类将 string 转换为 int

static void Main(string[] args)
{  
    string s = "123";

    try
    {
        int i = Convert.ToInt32(s);
    }
    catch (Exception ex)
    {
        // Pokemon exception handling, ideally rectification 
        // code and logging should be done here
    }    
}

关注点

在任何编程语言中,从一种类型到另一种类型的转换都是不可避免的。了解转换的基础知识将使我们在正确的场景中使用正确的方法。这篇简短的文章讨论了 C# 的相关内容。这是为初学者写的,所以一些有经验的程序员可能会觉得这篇文章没有价值。尽管如此,我希望它能提供信息。

历史

  • 2012 年 8 月 27 日:初版
© . All rights reserved.