在屬性和方法之間選擇
一般而言,方法表示動作,而屬性則表示資料。 屬性的使用與欄位一樣,這表示屬性在運算方面來說不應該是複雜的,或不應該產生副作用。 當它不違法下列方針時,請考慮使用屬性,而不要使用方法,因為屬性對於比較沒有經驗的開發人員比較容易使用。
如果成員表示型別的邏輯屬性,請考慮使用屬性。
例如,BorderStyle 是屬性 (Property),因為框線的樣式是 ListView 的屬性 (Attribute)。
如果屬性的值儲存在處理序記憶體中,則要使用屬性,而不要使用方法,然後此屬性就會提供該值的存取。
下列程式碼範例將說明這個方針。 EmployeeRecord 類別可定義兩個可為私用欄位提供存取的屬性。 完整的範例將顯示在這個主題的結尾。
Public Class EmployeeRecord
Private employeeIdValue as Integer
Private departmentValue as Integer
Public Sub New()
End Sub
Public Sub New (id as Integer, departmentId as Integer)
EmployeeId = id
Department = departmentId
End Sub
Public Property Department as Integer
Get
Return departmentValue
End Get
Set
departmentValue = value
End Set
End Property
Public Property EmployeeId as Integer
Get
Return employeeIdValue
End Get
Set
employeeIdValue = value
End Set
End Property
Public Function Clone() as EmployeeRecord
Return new EmployeeRecord(employeeIdValue, departmentValue)
End Function
End Class
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 ref class EmployeeRecord
{
private:
int employeeId;
int department;
public:
EmployeeRecord()
{
}
EmployeeRecord(int id, int departmentId)
{
EmployeeId = id;
Department = departmentId;
}
property int Department
{
int get() {return department;}
void set(int value) {department = value;}
}
property int EmployeeId
{
int get() {return employeeId;}
void set(int value) {employeeId = value;}
}
EmployeeRecord^ Clone()
{
return gcnew EmployeeRecord(employeeId, department);
}
};
在下列情況下一定要使用方法,而不要使用屬性。
這項作業是比欄位集原本還要慢的範圍排序。 如果您甚至考慮提供非同步版本的作業來避免執行緒的封鎖,則這個作業很有可能需要過多的成本來成為屬性。 特別是,存取網路或檔案系統的作業 (初始化的一次作業除外) 應該最有可能是方法,而不是屬性。
此作業為轉換時,例如 Object.ToString method。
此作業每次在呼叫時都會傳回不同的結果,即使參數未變更也是如此。 例如,NewGuid 方法在每次呼叫時,都會傳回不同的值。
此作業有一個非常明顯且可觀察到的副作用。 請注意,填入內部快取通常不會被視為可觀察到的副作用。
此作業會傳回內部狀態的複本 (這不包含堆疊上傳回的實值型別物件的複本)。
此作業會傳回陣列。
當作業為了保留內部陣列而傳回陣列時,請使用方法,您將必須傳回此陣列的深層複本,而不是屬性所使用之陣列的參考。 這項事實與開發人員將屬性當做欄位般使用的事實結合一起之後,可能會產生極無效率的程式碼。 下列程式碼範例中將說明這個情況,其中將會傳回使用屬性的陣列。 完整的範例將顯示在這個主題的結尾。
Public Class EmployeeData
Dim data as EmployeeRecord()
Public Sub New(data as EmployeeRecord())
Me.data = data
End Sub
Public ReadOnly Property Employees as EmployeeRecord()
Get
Dim newData as EmployeeRecord() = CopyEmployeeRecords()
Return newData
End Get
End Property
Private Function CopyEmployeeRecords() as EmployeeRecord()
Dim newData(UBound(data)) as EmployeeRecord
For i as Integer = 0 To UBound(data)
newData(i) = data(i).Clone()
Next i
Console.WriteLine ("EmployeeData: cloned employee data.")
Return newData
End Function
End Class
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 ref class EmployeeData
{
private:
array<EmployeeRecord^>^ data;
public:
EmployeeData(array<EmployeeRecord^>^ data)
{
this->data = data;
}
property array<EmployeeRecord^>^ Employees
{
array<EmployeeRecord^>^ get()
{
array<EmployeeRecord^>^ newData = CopyEmployeeRecords();
return newData;
}
}
private:
array<EmployeeRecord^>^ CopyEmployeeRecords()
{
array<EmployeeRecord^>^ newData = gcnew array<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 Shared Function FindEmployees( _
dataSource as EmployeeData, _
department as Integer) as Collection(Of Integer)
Dim storage as Collection(Of Integer) = new Collection(Of Integer)()
Console.WriteLine("Record checker: beginning search.")
For i as Integer = 0 To UBound(dataSource.Employees)
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)
End If
Next i
Return storage
End Function
End Class
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 RecordChecker
{
public:
static Collection<int>^ FindEmployees(EmployeeData^ dataSource,
int department)
{
Collection<int>^ storage = gcnew 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 類別錯誤地定義一個傳回陣列複本的屬性。
Imports System
Imports System.Collections.ObjectModel
Namespace Examples.DesignGuidelines.Properties
Public Class EmployeeRecord
Private employeeIdValue as Integer
Private departmentValue as Integer
Public Sub New()
End Sub
Public Sub New (id as Integer, departmentId as Integer)
EmployeeId = id
Department = departmentId
End Sub
Public Property Department as Integer
Get
Return departmentValue
End Get
Set
departmentValue = value
End Set
End Property
Public Property EmployeeId as Integer
Get
Return employeeIdValue
End Get
Set
employeeIdValue = value
End Set
End Property
Public Function Clone() as EmployeeRecord
Return new EmployeeRecord(employeeIdValue, departmentValue)
End Function
End Class
Public Class EmployeeData
Dim data as EmployeeRecord()
Public Sub New(data as EmployeeRecord())
Me.data = data
End Sub
Public ReadOnly Property Employees as EmployeeRecord()
Get
Dim newData as EmployeeRecord() = CopyEmployeeRecords()
Return newData
End Get
End Property
Private Function CopyEmployeeRecords() as EmployeeRecord()
Dim newData(UBound(data)) as EmployeeRecord
For i as Integer = 0 To UBound(data)
newData(i) = data(i).Clone()
Next i
Console.WriteLine ("EmployeeData: cloned employee data.")
Return newData
End Function
End Class
Public Class RecordChecker
Public Shared Function FindEmployees( _
dataSource as EmployeeData, _
department as Integer) as Collection(Of Integer)
Dim storage as Collection(Of Integer) = new Collection(Of Integer)()
Console.WriteLine("Record checker: beginning search.")
For i as Integer = 0 To UBound(dataSource.Employees)
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)
End If
Next i
Return storage
End Function
End Class
Public Class Tester
Public Shared Sub Main()
Dim records(2) as EmployeeRecord
Dim r0 as EmployeeRecord = new EmployeeRecord()
r0.EmployeeId = 1
r0.Department = 100
records(0) = r0
Dim r1 as EmployeeRecord = new EmployeeRecord()
r1.EmployeeId = 2
r1.Department = 100
records(1) = r1
Dim r2 as EmployeeRecord = new EmployeeRecord()
r2.EmployeeId = 3
r2.Department = 101
records(2) = r2
Dim empData as EmployeeData = new EmployeeData(records)
Dim hits as Collection(Of Integer)= _
RecordChecker.FindEmployees(empData, 100)
For Each i as Integer In hits
Console.WriteLine("found employee {0}", i)
Next i
End Sub
End Class
End Namespace
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);
}
}
}
}
using namespace System;
using namespace System::Collections::ObjectModel;
namespace Examples { namespace DesignGuidelines { namespace Properties
{
public ref class EmployeeRecord
{
private:
int employeeId;
int department;
public:
EmployeeRecord()
{
}
EmployeeRecord(int id, int departmentId)
{
EmployeeId = id;
Department = departmentId;
}
property int Department
{
int get() {return department;}
void set(int value) {department = value;}
}
property int EmployeeId
{
int get() {return employeeId;}
void set(int value) {employeeId = value;}
}
EmployeeRecord^ Clone()
{
return gcnew EmployeeRecord(employeeId, department);
}
};
public ref class EmployeeData
{
private:
array<EmployeeRecord^>^ data;
public:
EmployeeData(array<EmployeeRecord^>^ data)
{
this->data = data;
}
property array<EmployeeRecord^>^ Employees
{
array<EmployeeRecord^>^ get()
{
array<EmployeeRecord^>^ newData = CopyEmployeeRecords();
return newData;
}
}
private:
array<EmployeeRecord^>^ CopyEmployeeRecords()
{
array<EmployeeRecord^>^ newData = gcnew array<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 = gcnew 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 ref class Tester
{
public:
static void Main()
{
array<EmployeeRecord^>^ records = gcnew array<EmployeeRecord^>(3);
EmployeeRecord^ r0 = gcnew EmployeeRecord();
r0->EmployeeId = 1;
r0->Department = 100;
records[0] = r0;
EmployeeRecord^ r1 = gcnew EmployeeRecord();
r1->EmployeeId = 2;
r1->Department = 100;
records[1] = r1;
EmployeeRecord^ r2 = gcnew EmployeeRecord();
r2->EmployeeId = 3;
r2->Department = 101;
records[2] = r2;
EmployeeData^ empData = gcnew EmployeeData(records);
Collection<int>^ hits = RecordChecker::FindEmployees(empData, 100);
for each (int i in hits)
{
Console::WriteLine("found employee {0}", i);
}
}
};
}}}
Portions Copyright 2005 Microsoft Corporation. All rights reserved.
Portions Copyright Addison-Wesley Corporation. All rights reserved.
設計指引的詳細資訊,請參閱"框架設計準則:公約、 成語和可重複使用的模式。網路圖書館"書 Krzysztof Cwalina 和布拉德 · 艾布拉姆斯,2005年艾迪生 - 衛斯理,發表。