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是很难发现的,因为进行类型转换的代码是由编译器产生的,因此我们应该避免使用类型转换符。

  • 相关阅读:
    笔记-归并排序
    Repeated Substring Pattern
    Assign Cookies
    Number of Boomerangs
    Paint Fence
    Path Sum III
    Valid Word Square
    Sum of Two Integers
    Find All Numbers Disappeared in an Array
    First Unique Character in a String
  • 原文地址:https://www.cnblogs.com/wing011203/p/1652786.html
Copyright © 2011-2022 走看看