C#语言中struct的陷阱

  • 来源: 中国IT实验室 作者: sevenleaf   2010-04-27/11:10
  •       假设我们要为某大学写一个工资管理程序。首先是表示员工的 Employee 类(Employee.cs):

          01:  namespace Skyiv.Ben

          02:  {

          03:    class Employee

          04:    {

          05:      public string Department { get; private set; }

          06:      public string Name { get; private set; }

          07:      public decimal Salary { get; set; }

          08:

          09:      public Employee(string department, string name, decimal salary)

          10:      {

          11:        Department = department;

          12:        Name = name;

          13:        Salary = salary;

          14:      }

          15:

          16:      public override string ToString()

          17:      {

          18:        return string.Format("{0} {1} 工资:{2:N2}", Department, Name, Salary);

          19:      }

          20:    }

          21:  }

          接着是表示学校中各系的 Department 类(Department.cs):

          01:  namespace Skyiv.Ben

          02:  {

          03:    class Department

          04:    {

          05:      public string Name { get; private set; }

          06:      public int Count { get; private set; }

          07:      public decimal TotalSalary { get; private set; }

          08:

          09:      public Department(string name)

          10:      {

          11:        Name = name;

          12:      }

          13:

          14:      public void Add(Employee employee)

          15:      {

          16:        Count++;

          17:        TotalSalary += employee.Salary;

          18:      }

          19:

          20:      public override string ToString()

          21:      {

          22:        return string.Format("{0} 人数:{1} 总工资:{2:N2}", Name, Count, TotalSalary);

          23:      }

          24:    }

          25:  }

          最后就是主程序 Program.cs :

          01:  using System;

          02:  using System.Collections.Generic;

          03:

          04:  namespace Skyiv.Ben

          05:  {

          06:    class Program

          07:    {

          08:      static void Main()

          09:      {

          10:        new Program().Run();

          11:      }

          12:

          13:      void Run()

          14:      {

          15:        var employees = InitializeEmployees();

          16:        SalaryRaise(employees);

          17:        Statistic(employees);

          18:      }

          19:

          20:      List<Employee> InitializeEmployees()

          21:      {

          22:        var employees = new List<Employee>();

          23:        employees.Add(new Employee("校长室", "高松年", 72767.58m));

          24:        employees.Add(new Employee("政治系", "方鸿渐", 31982.45m));

          25:        employees.Add(new Employee("政治系", "赵辛楣", 40126.31m));

          26:        Console.WriteLine("三闾大学工资明细表:");

          27:        employees.ForEach(e => Console.WriteLine(e));

          28:        return employees;

          29:      }

          30:

          31:      void SalaryRaise(List<Employee> employees)

          32:      {

          33:        for (var i = 0; i < employees.Count; i++) employees[i].Salary += 10000;

          34:        Console.WriteLine(Environment.NewLine + "加薪之后:");

          35:        employees.ForEach(e => Console.WriteLine(e));

          36:      }

          37:

          38:      void Statistic(List<Employee> employees)

          39:      {

          40:        var departments = new Dictionary<string, Department>();

          41:        foreach (var employee in employees)

          42:        {

          43:          var name = employee.Department;

          44:          Department dep;

          45:          if (!departments.TryGetValue(name, out dep)) departments.Add(name, dep = new Department(name));

          46:          dep.Add(employee);

          47:        }

          48:        Console.WriteLine(Environment.NewLine + "三闾大学工资统计表:");

          49:        foreach (var kvp in departments) Console.WriteLine(kvp.Value);

          50:      }

          51:    }

          52:  }

          这个程序的运行结果如下所示:

          三闾大学工资明细表:

          校长室 高松年 工资:72,767.58

          政治系 方鸿渐 工资:31,982.45

          政治系 赵辛楣 工资:40,126.31

          加薪之后:

          校长室 高松年 工资:82,767.58

          政治系 方鸿渐 工资:41,982.45

          政治系 赵辛楣 工资:50,126.31

          三闾大学工资统计表:

          校长室 人数:1 总工资:82,767.58

          政治系 人数:2 总工资:92,108.76

          如果我们把 Employee 类(class)改为结构(struct),则在编译时就会报以下错误:

          CS1612: 无法修改“System.Collections.Generic.List.this[int]”的返回值,因为它不是变量。

          这个错误发生在 Program.cs 第 33 行中的 employees[i].Salary += 10000; 语句。

          我不理解这个 CS1612 错误,如果哪位朋友能够解释一下,请在评论中给出。谢谢!

          如果我们把 Department 类(class)改为结构(struct):

          01:  namespace Skyiv.Ben

          02:  {

          03:    struct Department

          04:    {

          05:      public string Name { get; private set; }

          06:      public int Count { get; private set; }

          07:      public decimal TotalSalary { get; private set; }

          08:

          09:      public Department(string name) : this()

          10:      {

          11:        Name = name;

          12:      }

          13:

          14:      public void Add(Employee employee)

          15:      {

          16:        Count++;

          17:        TotalSalary += employee.Salary;

          18:      }

          19:

          20:      public override string ToString()

          21:      {

          22:        return string.Format("{0} 人数:{1} 总工资:{2:N2}", Name, Count, TotalSalary);

          23:      }

          24:    }

          25:  }

    注意上述程序中第 09 行最后要加上“ : this() ”,否则 Microsoft C# 编译器会报错(但是 Mono C# 编译器不会报错,请参见:浅谈 Microsoft C# 编译器和 Mono C# 编译器 <http://www.cnblogs.com/skyivben/archive/2009/05/09/1453007.html>)。

          再次运行该程序,运行结果的最后三行如下所示:

          三闾大学工资统计表:

          校长室 人数:0 总工资:0.00

          政治系 人数:0 总工资:0.00

          这是因为现在的 Department 结构是值类型,而不是引用类型。所以在 Program.cs 第 46 行的 dep.Add(employee); 语句中,dep 的值的更改不会影响到 departments 字典中的值。所以统计出来的人数和总工资都是零了。

          要绕过这个陷阱很简单,在第 46 行的 dep.Add(employee); 语句后面加一句 departments[name] = dep; 就行了。

          在 .NET Framework Base Class Library 中,有很多的结构(struct)。如:

          ·System.Int32

          ·System.DateTime

          ·System.Drawing.Size

          使用时也要小心陷阱。

          此外,还有注意 DateTime 的 Add 和 AddDays 等方法并不更改此 DateTime 的值。而是返回一个新的 DateTime,其值是此运算的结果。因此以下语句是不成立的:

          for (var date = DateTime.MinValue; date < DateTime.Today; date.AddDays(1))

          正确的应该是:

          for (var date = DateTime.MinValue; date < DateTime.Today; date = date.AddDays(1))


    评论 {{userinfo.comments}}

    {{money}}

    {{question.question}}

    A {{question.A}}
    B {{question.B}}
    C {{question.C}}
    D {{question.D}}
    提交

    驱动号 更多