zoukankan      html  css  js  c++  java
  • Effective C# Item28:避免强制类型转换

        转换操作为类之间引入了一层“可替换性”,“替换”意味着一个类的实例可以被替换为另一个类的实例。例如,我们在一个类层次结构中,在任何使用父类的地方,我们可以使用子类的实例进行替代,这是“多态”的作用。

        当我们为类型定义了类型转换操作符后,我们实际上是在告诉编译器这些类型可以被当做目标类型来使用,这样的替换经常会导致一些很诡异的Bug,因为我们的类型可能并不是目标类型的完美替代品。

        我们来看下面的代码。

    代码
    1 public abstract class Employee
    2 {
    3 private string m_strName;
    4 public string Name
    5 {
    6 get { return m_strName; }
    7 set { m_strName = value; }
    8 }
    9
    10 private float m_fSalary;
    11 public float BaseSalary
    12 {
    13 get { return m_fSalary; }
    14 set { m_fSalary = value; }
    15 }
    16
    17 public Employee(string name, float salary)
    18 {
    19 m_strName = name;
    20 m_fSalary = salary;
    21 }
    22
    23 public Employee()
    24 {}
    25 }
    26
    27 public class Sales : Employee
    28 {
    29 public Sales(string name, float salary)
    30 : base(name, salary)
    31 { }
    32
    33 public Sales(Manager manager)
    34 {
    35 base.Name = manager.Name;
    36 base.BaseSalary = manager.BaseSalary;
    37 }
    38 }
    39
    40 public class Manager : Employee
    41 {
    42 public Manager(string name, float salary)
    43 : base(name, salary)
    44 { }
    45
    46 public static implicit operator Sales(Manager obj)
    47 {
    48 return new Sales(obj.Name, obj.BaseSalary) ;
    49 }
    50 }

        上面的代码定义了三个结构,其中Employee作为最上层的类型,被定义为abstract,它下面有两个派生类Sales和Manager,其中针对Manger向Sales提供了类型转换。

        下面是测试方法的代码。

    代码
    1 private static void Test()
    2 {
    3 Sales sales = new Sales("Wing", 2000);
    4 Manager manager = new Manager("UnKnown", 2000);
    5 Console.WriteLine("The First Test : Output info:");
    6 OuputInfo(sales);
    7 OuputInfo(manager);
    8 Console.WriteLine();
    9 Console.WriteLine("The Second Test : Double Salary:");
    10 Console.WriteLine("Before Double : ");
    11 Console.WriteLine(sales.BaseSalary);
    12 Console.WriteLine(manager.BaseSalary);
    13 DoubleSalary(sales);
    14 DoubleSalary(manager);
    15 Console.WriteLine("After Double : ");
    16 Console.WriteLine(sales.BaseSalary);
    17 Console.WriteLine(manager.BaseSalary);
    18 }
    19
    20 private static void DoubleSalary(Sales sales)
    21 {
    22 sales.BaseSalary *= 2;
    23 }
    24
    25 private static void OuputInfo(Sales sales)
    26 {
    27 Console.WriteLine(string.Format("Employee Info : {0}", sales.Name));
    28 }

        上面的代码包含了三个方法,其中Test()方法是测试的主方法,OutputInfo()方法用于输出员工的信息,DoubleSalary()方法用于将对象中的BaseSalary属性乘2(呵呵,快过年了,这就算是我的新年愿望啦^_^)。

        上述代码的执行结果如下图所示。

        我们可以看到,对于针对OutputInfo()方法进行的测试,Sales对象和Manager对象的结果是正确的;但是对于DoubleSalary()方法的测试结果,就有问题了。在调用DoubleSalary()方法后,Sales的BaseSalary属性确实变为之前的2倍了,但是Manager的BaseSalary属性并没有发生变化。

        之所以出现这种情况,是因为当将Manager对象传入DoubleSalary()方法中时,发生了隐式类型转换,即将Manager类型转变为Sales类型,这时会创建出一个新的临时对象,而DoubleSalary()方法中的各种操作,都是针对临时对象的,在退出DoubleSalary()方法后,临时对象变为垃圾,在稍后就会被垃圾回收器进行回收处理。

        其实下面三句话的执行结果是一样的。

    DoubleSalary(manager);
    DoubleSalary((Sales)manager);
    DoubleSalary(
    new Sales(manager));

        如果希望解决这个问题,需要将代码写成下面的样子。

    Sales temp = new Sales(manager);
    DoubleSalary(temp);

        上面代码执行后,temp对象的BaseSalary属性已经翻倍了。

        总结:转换操作符所获得的“替换性”会为代码带来一些问题,提供转换操作符的意思是在向类的用户表明,在本该使用这个类的地方,用户可以使用其他类型来代替。当访问被替换的对象时,和客户程序打交道的实际上时一些临时对象或者内部字段。这些临时对象被修改后,结果就会被抛弃。这样诡异的bug是很难发现的,因为进行类型转换的代码是由编译器产生的,因此我们应该避免使用类型转换符。

  • 相关阅读:
    WebSocket来实现即时通讯
    微信小程序1
    使用phpqrcode来生成二维码/thinkphp
    PHP函数积累
    Docker 常用命令汇总(beta)
    Nginx+Keepalived高可用架构简述(beta)
    Docker镜像制作(以Nginx+Keepalived为例)(beta)
    开源协议浅谈(beta)
    【Elasticsearch系列】ES安装(mac)
    linux 下安装JDK
  • 原文地址:https://www.cnblogs.com/wing011203/p/1652786.html
Copyright © 2011-2022 走看看