Export (0) Print
Expand All

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 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);
    }
}

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
{
    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;
    }
}

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 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;
    }
}

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.

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.

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);
            }
        }
    }
}

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.

Community Additions

ADD
Show:
© 2014 Microsoft