【问题标题】:What is the logic behind casting classes in c#?在 C# 中强制转换类背后的逻辑是什么?
【发布时间】:2018-02-15 13:02:47
【问题描述】:

我试图了解一些 C# 转换条件背后的逻辑,这是我的测试代码

文件:example.cs

public class Animal { public string animal_name = "Animal"; }

public class Dog : Animal { public string dog_name = "Dog"; }

public class Class1
{
    public void createObjects()
    {
    var animal1 = new Animal();
    printAnimalName(animal1);
    }

    public void printAnimalName(Animal my_animal)
    {
    var dog1 = my_animal as Dog; // dog1 is of type Dog
    Console.WriteLine(dog1.dog_name);
    }
}

在我的 Main 函数中,我调用 createObjects 函数如下:

    static void Main(string[] args)
    {
        Class1 c1 = new Class1();
        c1.createObjects();
        Console.ReadLine();
    }

运行上面的代码会报错

System.NullReferenceException:'Object reference not set to an instance of an object'

我知道这是应该的方式,由于演员:

var dog1 = my_animal as Dog;

但它背后的逻辑是什么?为什么我们不能通过传递一个 Animal 对象来调用函数 printAnimalName 呢?根据我的理解,这应该是可能的,因为该函数需要一个 Animal 对象。

【问题讨论】:

  • animal1 是 Animal 类型,而不是 Dog 类型。尝试使 Animal 成为一个抽象类,看看会发生什么。你应该像这样使用它: var animal1 = new Dog();
  • as 会尝试投射它,如果它不能投射它会返回default
  • if (my_animal is Dog dog1) Console.WriteLine(dog1.dog_name); else Console.WriteLine("Not a dog!"); 使用较新的 c# 通过 is 进行转换是首选。使用as,您需要强制转换&& 测试,使用is,您需要包含测试。
  • 如果您只尝试使用已知的Animal 而不是尝试转换为Dog,它将起作用。如果您需要Dog,则方法的参数应采用Dog 而不是Animal
  • 你的例子不太好。首先,一条狗将同时拥有dog_nameanimal_name。其次,如果你想在你的方法中处理一个dog 实例,你应该传递一个dog 实例给它。如果在方法内部只能使用派生类,则没有必要将基类作为参数。

标签: c#


【解决方案1】:

var dog1 = my_animal as Dog; // dog1 is of type Dog 之后,您只需要添加空检查:

if(dog1 != null)
{
    Console.WriteLine(dog1.dog_name);
}

【讨论】:

  • 为什么不直接使用“Console.WriteLine(dog1?.dog_name);”。为您节省一些支票
  • 如果需要显示 null 然后是
【解决方案2】:

我认为你需要了解多态性、抽象类和接口。

public abstract class FourLeggedAnimal
{
    public int GetLegCount()
    {
        return 4;
    }
}

public class Dog : FourLeggedAnimal
{
    public string GetScientificName()
    {
        return "doggus scientificus";
    }
}

public class Cat : FourLeggedAnimal
{
    public string GetServant()
    {
        return "human";
    }
}

public class AnimalInformer
{
    public void DisplayInformation(FourLeggedAnimal animal)
    {
        Console.WriteLine("It has {0} legs", animal.GetLegCount());

        if (animal is Dog)
            Console.WriteLine("Its scientific name is {0}", ((Dog)animal).GetScientificName());
        if (animal is Cat)
            Console.WriteLine("Its servant is {0}", ((Cat)animal).GetServant());
    }
}

在这里,您使用抽象类为从它派生的所有其他类提供基本功能。从FourLeggedAnimal 派生的所有类都有一个方法GetLegCount(),它返回腿数。

但是猫有一个狗没有的仆人,它只有一个朋友(都是人类,但关系不同)。所以狗不需要方法“GetServant”,但猫需要。 -> 2 个单独的类中的不同实现

接口的另一个例子是每个派生类都需要提供该功能。

public interface IMovableObject
{
    int GetMaxSpeed();
}

public class Car : IMovableObject
{
    public int GetMaxSpeed()
    {
        return 100;
    }
}

public class Human : IMovableObject
{
    public int GetMaxSpeed()
    {
        return 20;
    }
}
public static class SpeedChecker
{
    public static void CheckSpeed(IMovableObject speedster)
    {
        Console.WriteLine("Checking Speed..");

        int speed = speedster.GetMaxSpeed();
        if (speed > 50)
            Console.WriteLine("It's really fast!");
        else
            Console.WriteLine("Just a turtle or something similar...");
    }
}

现在,如果您有一个方法获取一个实际上是汽车的 IMovableObject,则调用 Car 的实现:

Car c = new Car();
Human h = new Human();

Console.WriteLine("Checking Car..");
SpeedChecker.CheckSpeed(c);
Console.WriteLine("Checking Human..");
SpeedChecker.CheckSpeed(h);

-> 返回:

Checking Car...
Checking Speed...
It's really fast!
Checking Human...
Checking Speed...
Just a turtle or something similar...

这些是您派生类并使用强制转换来获得某些功能或使用基本类型而不进行强制转换但仍然获得不同功能的两种用途

【讨论】:

    【解决方案3】:

    你的问题在这里:

    public void printAnimalName(Animal my_animal)
    {
      var dog1 = my_animal as Dog; // dog1 is of type Dog
      Console.WriteLine(dog1.dog_name);  //Animal does not have this property!
    }
    

    铸造不调用构造函数。这意味着dog_name 为空,因为my_animal 没有dog_name 属性。我想你在这里遗漏了一些关于继承的东西。

    这实际上是一个在现实世界中以更复杂的形式发生的例子;给定类AB 继承自A。由于继承,两者具有相同的属性。然后有人用相似但不协调的属性名称创建了一个不同的属性,并将其用于子对象中的相同功能。欢迎来到码气味城。

    要修复你的函数,让它看起来像一条狗,你需要做两件事:

        public class Animal { public string animal_name = "Animal"; }
    
        //since we want a different default, we can 
        //can make the change in the constructor
        public class Dog : Animal 
        {  
            Dog(){ this.animal_name = "Dog"; }
            //if you really, really want a second name string, you can do this:
            public string Dog_Name 
            {
               get { return this.animal_name; } 
               set { this.animal_name = value; }
            }
        }
    

    然后,你需要让你的方法调用适当的属性。

      public void printAnimalName(Animal my_animal)
      {
        var dog1 = my_animal as Dog; // dog1 is of type Dog
        Console.WriteLine(dog1.animal_name);
      }
    

    我还建议将您的公共字段更改为properties 和可能的override ToString(),当您想要对一个对象做的只是返回一个表示它的字符串时。

    【讨论】:

    • 不是dog_namenull,而是dog1null
    • 绝对不想在这里使用new关键字。如果这样做,当引用是基类类型时,您将获得基类实现。 See this demo.
    • 我删除了 New 关键字,因为您是对的。将其替换为如何从属性调用字段,因为查询者需要开始使用属性。
    • 如果通过另一种动物,为什么这种方法会失败?除非给它一只狗,否则这个实现会失败。
    • 我将回答限制在问题的范围内。这确实是一个问题。如果不是Dog 或派生自Dog 并且没有其他可接受的输入,则会抛出异常。但是,异常处理是根据您需要处理的位置和方式来完成的,因此它超出了问题的范围。
    猜你喜欢
    • 2017-10-17
    • 2020-09-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-07-15
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多