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.

See Also

Concepts

Property Design

Other Resources

Member Design Guidelines

Design Guidelines for Developing Class Libraries