Choix entre des propriétés et des méthodes

En général, les méthodes représentent des actions et les propriétés représentent des données. Les propriétés sont conçues pour être utilisées comme des champs, ce qui signifie que les propriétés ne doivent pas être complexes en termes de calcul ni produire des effets secondaires. Si son utilisation n'est pas contraire aux règles suivantes, envisagez d'utiliser une propriété plutôt qu'une méthode dans la mesure où l'utilisation de propriétés est considérée plus simple pour les développeurs moins expérimentés.

Envisagez d'utiliser une propriété si le membre représente un attribut logique du type.

Par exemple, BorderStyle est une propriété puisque le style de la bordure est un attribut de ListView.

Utilisez une propriété au lieu d'une méthode si la valeur de la propriété est stockée dans la mémoire du processus et que la propriété fournit simplement l'accès à la valeur.

L'exemple de code suivant illustre cette règle. La classe EmployeeRecord définit deux propriétés qui fournissent un accès à des champs privés. L'exemple complet est illustré à la fin de cette rubrique.

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

Utilisez une méthode au lieu d'une propriété dans les situations suivantes.

  • L'opération est considérablement plus lente que ne le serait un champ set. Si vous envisagez même de fournir une version asynchrone d'une opération afin d'éviter le blocage du thread, il est fort probable que l'opération soit trop coûteuse pour utiliser une propriété. En particulier, les opérations qui accèdent au réseau ou au système de fichiers (dans d'autres circonstances que l'accès requis pour l'initialisation) doivent probablement être des méthodes et non des propriétés.

  • L'opération est une conversion, telle que Object.ToString method.

  • À chaque appel, l'opération retourne un résultat différent, même si les paramètres ne changent pas. Par exemple, la méthode NewGuid retourne une valeur différente chaque fois qu'elle est appelée.

  • L'opération a un effet secondaire notable et facilement observable. Notez que le remplissage d'un cache interne n'est généralement pas considéré comme un effet secondaire observable.

  • L'opération retourne une copie d'un état interne (cela n'inclut pas des copies d'objets de type valeur retournées sur la pile).

  • L'opération retourne un tableau.

Utilisez une méthode dans le cas où l'opération retourne un tableau. En effet, pour conserver le tableau interne, vous devez normalement retourner une copie complète du tableau et non une référence au tableau utilisé par la propriété. Si vous ajoutez à cela le fait que les développeurs utilisent des propriétés comme s'il s'agissait de champs, votre code risque d'être complètement inefficace. Cela est illustré dans l'exemple de code suivant, lequel retourne un tableau à l'aide d'une propriété. L'exemple complet est illustré à la fin de cette rubrique.

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

Un développeur qui utilise cette classe suppose que la propriété n'est pas plus coûteuse que l'accès à un champ et écrit le code d'application en se fondant sur cette supposition, comme l'illustre l'exemple de code suivant.

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

Notez que la propriété Employees est non seulement accédée dans chaque itération de boucle mais aussi lorsque les services correspondent. À chaque accès de la propriété, une copie du tableau Employees est créée et utilisée brièvement avant l'exécution du garbage collection requis. En implémentant Employees comme une méthode, vous indiquez aux développeurs que cette action est plus coûteuse en termes de calcul que l'accès à un champ. Il est probable que les développeurs décident d'appeler une fois une méthode et de mettre en cache les résultats de l'appel à une méthode pour effectuer leur traitement.

Exemple

L'exemple de code suivant illustre une application complète supposant que l'accès à une propriété est peu coûteux en termes de calcul. La classe EmployeeData définit incorrectement une propriété qui retourne une copie d'un tableau.

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. Tous droits réservés.

Portions Copyright Addison-Wesley Corporation. Tous droits réservés.

Pour plus d'informations sur les règles de conception, consultez « règles de conception d'infrastructure : Conventions idiomes et modèles carnet de bibliothèques réutilisables framework » Krzysztof Cwalina et Brad Abrams, publiés par Addison-Wesley, 2005.

Voir aussi

Concepts

Conception des propriétés

Autres ressources

Instructions de conception des membres

Instructions de conception pour le développement de bibliothèques de classes