通常,方法用来呈现动作而属性用来呈现数据。并且属性能够与字段一样被使用,因此说明了属性不应该是复杂的计算或者会导致副作用的。在不违反下列指导方针的时候,考虑属性的使用会胜于方法,因为有较少体验的开发者会发现属性是更加容易被使用的。
如果成员呈现了类型的一个逻辑特性,考虑使用属性。
例如,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); } }
在下列情况下,方法的使用要胜于属性。
- 操作是比字段集更慢的命令。如果你考虑提供一个异步版本的操作来避免线程的阻塞,那么作为属性来说它将与代价昂贵的操作是非常相似的。尤其是在访问网络或文件系统(不同于一次性的初始化)的时候最应该使用方法,而不是属性。
- 操作是一个转换(如 Object.ToString 方法)。
- 操作在每次被调用的时候都返回一个不同的结果,甚至连参数都没有任何变化。例如,NewGuid 方法就在每次被调用的时候都会返回一个不同的值。
- 操作有一个重大的并且可测的副面作用。注意,操作一个内部的缓存通常不会被认为是一个可测的副面作用。
- 操作返回一个内部状态(这不包括在堆栈中被返回的对象类型值的复制)的副本。
- 操作返回一个数组。
在操作返回一个数组的时候使用方法,因为要保护内部数组,所以你应该返回该数组的一个纵深的复制,而不是通过属性而被使用的数组的一个引用。这与开发者把属性当成字段一样来使用的事实是相符合的,并且能够导致非常低效的代码。这就是在下列代码范例中将要被说明的:使用一个属性来返回一个数组。完整的范例将在本文的最后部分被列出。
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); } } } }