通常,方法用来呈现动作而属性用来呈现数据。并且属性能够与字段一样被使用,因此说明了属性不应该是复杂的计算或者会导致副作用的。在不违反下列指导方针的时候,考虑属性的使用会胜于方法,因为有较少体验的开发者会发现属性是更加容易被使用的。
例如,BorderStyle 就可以作为一个属性,因为边框的样式是 ListView 的一个特性。
下列代码范例就说明了这个指导方针。EmployeeRecord 类定义了两个对私有字段进行访问的属性。完整的范例将在本文的最后部分被列出。
public class EmployeeRecord { private int employeeId; private int department; public EmployeeRecord() { } public EmployeeRecord (int id, int departmentId) { EmployeeId = id; Department = departmentId; } public int Department { get {return department;} set {department = value;} } public int EmployeeId { get {return employeeId;} set {employeeId = value;} } public EmployeeRecord Clone() { return new EmployeeRecord(employeeId, department); } }
在操作返回一个数组的时候使用方法,因为要保护内部数组,所以你应该返回该数组的一个纵深的复制,而不是通过属性而被使用的数组的一个引用。这与开发者把属性当成字段一样来使用的事实是相符合的,并且能够导致非常低效的代码。这就是在下列代码范例中将要被说明的:使用一个属性来返回一个数组。完整的范例将在本文的最后部分被列出。
public class EmployeeData { EmployeeRecord[] data; public EmployeeData(EmployeeRecord[] data) { this.data = data; } public EmployeeRecord[] Employees { get { EmployeeRecord[] newData = CopyEmployeeRecords(); return newData; } } EmployeeRecord[] CopyEmployeeRecords() { EmployeeRecord[] newData = new EmployeeRecord[data.Length]; for(int i = 0; i< data.Length; i++) { newData[i] = data[i].Clone(); } Console.WriteLine ("EmployeeData: cloned employee data."); return newData; } }
开发者使用这个类假设了属性并不是更加昂贵的(相对于字段访问和编写如下代码范例所示的基于这个设想的应用程序代码而言)。
public class RecordChecker { public static Collection<int> FindEmployees(EmployeeData dataSource, int department) { Collection<int> storage = new Collection<int>(); Console.WriteLine("Record checker: beginning search."); for (int i = 0; i < dataSource.Employees.Length; i++) { if (dataSource.Employees[i].Department == department) { Console.WriteLine("Record checker: found match at {0}.", i); storage.Add(dataSource.Employees[i].EmployeeId); Console.WriteLine("Record checker: stored match at {0}.", i); } else { Console.WriteLine("Record checker: no match at {0}.", i); } } return storage; } }
注意:Employees 属性在每个重复循环中被访问,并且同样也在部门相匹配的时候被访问。在属性每次被访问的时候,都会复制一个被创建的、临时被使用的,然后需要进行垃圾回收的雇员数组。通过把 Employees 实现成一个方法,你可以向开发者表示这个动作是比访问字段时更加昂贵的计算。开发者很有可能只对方法调用一次并且把方法调用的结果进行缓存来完成他们的处理。
下列代码范例说明了假设对属性的访问是廉价的一个完整的应用程序。并且 EmployeeData 类错误地定义了一个返回数组副本的属性。
using System; using System.Collections.ObjectModel; namespace Examples.DesignGuidelines.Properties { public class EmployeeRecord { private int employeeId; private int department; public EmployeeRecord() { } public EmployeeRecord (int id, int departmentId) { EmployeeId = id; Department = departmentId; } public int Department { get {return department;} set {department = value;} } public int EmployeeId { get {return employeeId;} set {employeeId = value;} } public EmployeeRecord Clone() { return new EmployeeRecord(employeeId, department); } } public class EmployeeData { EmployeeRecord[] data; public EmployeeData(EmployeeRecord[] data) { this.data = data; } public EmployeeRecord[] Employees { get { EmployeeRecord[] newData = CopyEmployeeRecords(); return newData; } } EmployeeRecord[] CopyEmployeeRecords() { EmployeeRecord[] newData = new EmployeeRecord[data.Length]; for(int i = 0; i< data.Length; i++) { newData[i] = data[i].Clone(); } Console.WriteLine ("EmployeeData: cloned employee data."); return newData; } } public class RecordChecker { public static Collection<int> FindEmployees(EmployeeData dataSource, int department) { Collection<int> storage = new Collection<int>(); Console.WriteLine("Record checker: beginning search."); for (int i = 0; i < dataSource.Employees.Length; i++) { if (dataSource.Employees[i].Department == department) { Console.WriteLine("Record checker: found match at {0}.", i); storage.Add(dataSource.Employees[i].EmployeeId); Console.WriteLine("Record checker: stored match at {0}.", i); } else { Console.WriteLine("Record checker: no match at {0}.", i); } } return storage; } } public class Tester { public static void Main() { EmployeeRecord[] records = new EmployeeRecord[3]; EmployeeRecord r0 = new EmployeeRecord(); r0.EmployeeId = 1; r0.Department = 100; records[0] = r0; EmployeeRecord r1 = new EmployeeRecord(); r1.EmployeeId = 2; r1.Department = 100; records[1] = r1; EmployeeRecord r2 = new EmployeeRecord(); r2.EmployeeId = 3; r2.Department = 101; records[2] = r2; EmployeeData empData = new EmployeeData(records); Collection<int> hits = RecordChecker.FindEmployees(empData, 100); foreach (int i in hits) { Console.WriteLine("found employee {0}", i); } } } }