转换接口和类对象

3
将对象强制转换为接口和将对象强制转换为类有什么区别,就像这个例子一样。
namespace ConsoleApplication1
{
    interface IAnimal
    {
        string Sound();
    }

    class Animal : IAnimal
    {
        public string Sound()
        {
            return "Animal sound";
        }
    }

    class Lion : Animal, IAnimal
    {
        public string Sound()
        {
            return "Roarrrrr";
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Lion lion = new Lion();
            IAnimal animal = (Animal)lion; // variant 1

            IAnimal animal2 = (IAnimal)lion; // variant 2

            Console.WriteLine(animal.Sound());
        }
    }
}

variant 1和variant 2有什么差别?

我猜你会得到一个编译器错误,或者至少是一个编译器警告,比如“如果你打算重写函数,请使用关键字new来创建Sound”。 - user287107
4个回答

3

In

IAnimal animal = (Animal)lion;

这里发生了一个隐式转换,因为animal被声明为IAnimal,而Animal可以转换为IAnimal(因为该类实现了该接口)。

这相当于写成:

Lion lion = new Lion();
Animal a = lion;
IAnimal ia = a;

所有这些转换都是可能的。
但是,您也可以只写成以下形式:
IAnimal lion = new Lion();

另一方面,如果您写了以下内容:
var animal = (Animal)lion;

animal将成为Animal类的一个实例。


我能理解这个(IAnimal)狮子等同于这个 IAnimal animal = new Lion 吗? - theChampion

3
事实上,在编译后的IL代码中,没有任何区别。但是当你将“Animal”赋值给“IAnimal”时,编译器会执行一种隐式转换。编译器之所以能够这样做,是因为它知道“Animal”实现了“IAnimal”。以下是IL代码,可以更好地说明这一点。
IL_0001:  newobj      UserQuery+Lion..ctor //Create new lion
IL_0006:  stloc.0     // store it in variable lion
IL_0007:  ldloc.0     // load variable lion
IL_0008:  stloc.1     // store it in variable animal
IL_0009:  ldloc.0     // load variable lion
IL_000A:  stloc.2     // store it in variable animal2
IL_000B:  ldloc.1     // load variable animal
IL_000C:  callvirt    UserQuery+IAnimal.Sound //Call animal.Sound 
IL_0011:  call        System.Console.WriteLine //With the result call Console.WriteLine

3
唯一的区别在于编译器如何检查类型转换是否允许。
在第一种变体中,编译器检查如何将 Lion 转换为 Animal,然后检查如何将 Animal 转换为 IAnimal。由于这两个转换都可以安全地进行,因为 Lion 是一个 Animal,而 Animal 是一个 IAnimal,所以编译器根本不会生成任何转换代码,它只是一个赋值操作。
在第二种变体中,编译器检查如何将 Lion 转换为 IAnimal,然后检查 IAnimal 是否与 IAnimal 相同。由于转换可以安全地进行,它也不会在此处生成任何转换代码,它也只是一个赋值操作。
由于 Lion 是一个 IAnimal,所以您根本不需要进行任何转换,只需将其分配给变量即可:
IAnimal animal3 = lion;

在这种情况下,编译器会检查如何将 Lion 转换为 IAnimal,由于可以安全地进行转换,因此它不会生成任何转换代码,只生成赋值语句。

所以,将(Animal)lion 和 (IAnimal)lion 强制转换为 IAnimal对象时没有任何区别,因为对象的类型是 Lion 对吧? - theChampion
@theChampion:没有区别,因为AnimalIAnimal都可以安全地分配给IAnimal变量。一旦你将类型转换为AnimalIAnimal,编译器就不会关心实际类型是Lion。当你将引用分配给变量时,决定它是否可以分配给变量的是引用的类型,而不是它所引用的对象的实际类型。 - Guffa
我可以理解这个(IAnimal)狮子等于这个 IAnimal animal = new Lion 吗? - theChampion
@theChampion:完全不相等,但我认为你指的是在两者中进行的转换,这是相同的。在两种情况下,Lion 引用都被转换为 IAnimal 引用,并且是由编译器而不是运行时完成的。第一个是显式转换,而第二个是隐式转换。 - Guffa
为了让编译器理解这行代码 IAnimal animal = (Animal)lion,需要进行以下两个步骤:第一步 - 将 (Animal)lion 转换成 Animal a = lion第二步 - 再将 a 转换成 IAnimal animal = a - theChampion
1
@theChampion:是的,你可以这样想。所有的类型转换都只发生在编译器的思维中,在最终的代码中,“Lion”引用只是被赋值给了一个“IAnimal”变量,将其转换为一个“IAnimal”引用而不需要任何检查或转换。编译器在编译时进行检查,并确定在运行时不需要进行任何检查。 - Guffa

1
在您的第一个示例中,您正在执行显式转换为 Animal ,然后进行隐式转换以将其转换为 IAnimal ,类似于 IAnimal animal =(IAnimal)(Animal)lion; 。第二个示例只是执行隐式转换,并且在所有情况下,您可以删除第二个示例中的强制转换。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接