Choosing Between Properties and Methods
In general, methods represent actions and properties represent data. Properties are meant to be used like fields, meaning that properties should not be computationally complex or produce side effects. When it does not violate the following guidelines, consider using a property, rather than a method, because less experienced developers find properties easier to use.
Consider using a property if the member represents a logical attribute of the type.
For example, BorderStyle is a property because the style of the border is an attribute of a ListView.
Do use a property, rather than a method, if the value of the property is stored in the process memory and the property would just provide access to the value.
The following code example illustrates this guideline. The EmployeeRecord class defines two properties that provide access to private fields. The complete example is shown at the end of this topic.
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);
}
};
Do use a method, rather than a property, in the following situations.
The operation is orders of magnitude slower than a field set would be. If you are even considering providing an asynchronous version of an operation to avoid blocking the thread, it is very likely that the operation is too expensive to be a property. In particular, operations that access the network or the file system (other than once for initialization) should most likely be methods, not properties.
The operation is a conversion, such as the Object.ToString method.
The operation returns a different result each time it is called, even if the parameters do not change. For example, the NewGuid method returns a different value each time it is called.
The operation has a significant and observable side effect. Note that populating an internal cache is not generally considered an observable side effect.
The operation returns a copy of an internal state (this does not include copies of value type objects returned on the stack).
The operation returns an array.
Use a method where the operation returns an array because to preserve the internal array, you would have to return a deep copy of the array, not a reference to the array used by the property. This fact, combined with the fact that developers use properties as though they were fields, can lead to very inefficient code. This is illustrated in the following code example, which returns an array using a property. The complete example is shown at the end of this topic.
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;
}
};
A developer using this class assumes that the property is no more expensive than a field access and writes application code based on that assumption as shown in the following code example.
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;
}
};
Note that the Employees property is accessed in each loop iteration and is also accessed when the departments match. Each time the property is accessed, a copy of the employees array is created, used briefly, and then requires garbage collection. By implementing Employees as a method, you indicate to developers that this action is more computationally expensive than accessing a field. Developers are more likely to call a method once and cache the results of the method call to perform their processing.
Example
The following code example shows a complete application that assumes that a property access is computationally inexpensive. The EmployeeData class incorrectly defines a property that returns a copy of an array.
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.
For more information on design guidelines, see the "Framework Design Guidelines: Conventions, Idioms, and Patterns for Reusable .NET Libraries" book by Krzysztof Cwalina and Brad Abrams, published by Addison-Wesley, 2005.