Share via


プロパティの使用方法のガイドライン

プロパティとメソッドのどちらがより要件に適しているかを判断してください。プロパティまたはメソッドのどちらを選択すべきかについては、「プロパティとメソッド」を参照してください。

推奨されるプロパティの名前付けのガイドラインに基づいて、プロパティ名を選択します。

set アクセサを使用してプロパティにアクセスするときは、そのプロパティの値を変更する前に保存しておきます。これによって、set アクセサが例外をスローした場合でも、データが失われることはなくなります。

プロパティの状態について

プロパティは、任意の順序で設定できます。プロパティは、別のプロパティとの関係においては、状態なしです。開発者が特定のプロパティ セットを指定するか、そのオブジェクトが特定の状態になるまでは、オブジェクトの特定の機能が無効になっていることはよくあります。このオブジェクトが適切な状態になるまで、その機能をアクティブにすることはできません。この機能は、オブジェクトが適切な状態になると自動的にアクティブになるため、明示的に呼び出す必要はありません。このセマンティクスは、開発者がプロパティの値を設定する順序、または開発者がオブジェクトをアクティブな状態にした方法にかかわらず同じです。

たとえば、TextBox コントロールには、2 つの関連するプロパティ DataSourceDataField があります。DataSource はテーブル名を指定し、DataField は列名を指定します。両方のプロパティが指定されると、コントロールの Text プロパティに、テーブルからデータが自動的に連結されます。任意の順序で設定できるプロパティを次のコード例に示します。

Dim t As New TextBox()
t.DataSource = "Publishers"
t.DataField = "AuthorID"
' The data-binding feature is now active.
[C#]
TextBox t = new TextBox();
t.DataSource = "Publishers";
t.DataField = "AuthorID";
// The data-binding feature is now active.

DataSource プロパティと DataField プロパティは、任意の順序で設定できます。そのため、前のコードによる結果は次のコードの結果と同じです。

Dim t As New TextBox()
t.DataField = "AuthorID"
t.DataSource = "Publishers"
' The data-binding feature is now active.

[C#]
TextBox t = new TextBox();
t.DataField = "AuthorID";
t.DataSource = "Publishers";
// The data-binding feature is now active.

プロパティを null (Visual Basic では Nothing) に設定することで、その値が指定されていないことを示すこともできます。

Dim t As New TextBox()
t.DataField = "AuthorID"
t.DataSource = "Publishers"
' The data-binding feature is now active.
t.DataSource = Nothing
' The data-binding feature is now inactive.
[C#]
TextBox t = new TextBox();
t.DataField = "AuthorID";
t.DataSource = "Publishers";
// The data-binding feature is now active.
t.DataSource = null;
// The data-binding feature is now inactive.

データ連結機能の状態を追跡し、その機能を適切なタイミングでアクティブまたは非アクティブにする方法を次のコード例に示します。

Public Class TextBox
   Private m_dataSource As String
   Private m_dataField As String
   Private m_active As Boolean

   Public Property DataSource() As String
      Get
         Return m_dataSource
      End Get
      Set
         If value <> m_dataSource Then
            ' Set the property value first, in case activate fails.
            m_dataSource = value
            ' Update active state.
            SetActive(( Not (m_dataSource Is Nothing) And Not (m_dataField Is Nothing)))
         End If
      End Set
   End Property
   Public Property DataField() As String
      Get
         Return m_dataField
      End Get
      Set
         If value <> m_dataField Then
            ' Set the property value first, in case activate fails.
            m_dataField = value
            ' Update active state.
            SetActive(( Not (m_dataSource Is Nothing) And Not (m_dataField Is Nothing)))
         End If
      End Set
   End Property
   Sub SetActive(m_value As Boolean)
      If value <> m_active Then
         If m_value Then
            Activate()
            Text = dataBase.Value(m_dataField)
         Else
            Deactivate()
            Text = ""
         End If
         ' Set active only if successful.
         m_active = value
      End If
   End Sub
   Sub Activate()
      ' Open database.
   End Sub
 
   Sub Deactivate()
      ' Close database.
   End Sub
End Class
[C#]
public class TextBox
{
   string dataSource;
   string dataField;
   bool active;

   public string DataSource
   {   
      get
      {
         return dataSource;
      }
      set
      {
         if (value != dataSource)
         {
            // Update active state.
            SetActive(value != null && dataField != null);
            dataSource = value;
         }
      }
      }
      
   public string DataField
   {
      get
      {
         return dataField;
      }
      set
      {
         if (value != dataField)
         {
            // Update active state.
            SetActive(dataSource != null && dataField != null);
            dataField = value;
         }
      }
   }   
   void SetActive(Boolean value)
   {
      if (value != active)
      {
         if (value)
         {
            Activate();
            Text = dataBase.Value(dataField);
         }
         else
         {
            Deactivate();
            Text = "";
         }
         // Set active only if successful.
         active = value; 
      }
   }
   void Activate()
   {
      // Open database.
   }
      
   void Deactivate()
   {
      // Close database.
   }
}

前の例では、次の式を使用して、オブジェクトが、データ連結機能を自動的にアクティブ化できる状態かどうかを判断しています。

(Not (value Is Nothing) And Not (m_dataField Is Nothing))
[C#]
value != null && dataField != null

機能の自動アクティブ化を実現するには、オブジェクトが特定の状態になったときにアクティブ化できるかどうかを判断してから、必要に応じてそのオブジェクトをアクティブにするメソッドを作成します。

Sub UpdateActive(m_dataSource As String, m_dataField As String)
   SetActive(( Not (m_dataSource Is Nothing) And Not (m_dataField Is Nothing)))
End Sub
[C#]
void UpdateActive(string dataSource, string dataField)
{
   SetActive(dataSource != null && dataField != null);
}

DataSourceDataMember などの関連するプロパティがある場合は、ISupportInitialize Interface を実装することを検討してください。そうすることにより、デザイナ (開発者) が、コンポーネントに最適化機能を実装できるように複数のプロパティを設定するときに、ISupportInitialize.BeginInit メソッドや ISupportInitialize.EndInit メソッドを呼び出せるようになります。上の例で ISupportInitialize を使用すると、セットアップが適切に完了するまでの間、データベースへの不要なアクセス試行が行われないようにすることができます。

このメソッドで使用されている式は、このような状態の変化を強制的に行うために考察しておく必要がある、オブジェクト モデルの一部を示しています。この場合は、DataSource プロパティと DataField プロパティが影響を受けています。プロパティまたはメソッドのどちらを選択すべきかについては、「プロパティとメソッド」を参照してください。

プロパティ変更イベントの発生

コンポーネントのプロパティがプログラムによって変更されたときに、コンポーネントのコンシューマに変更について通知するには、プロパティ変更イベントを発生させます。プロパティ変更イベントの名前付け規則に従い、プロパティ名にサフィックス Changed を追加して TextChanged のような名前にします。たとえば、Text プロパティが変更されたときに、コントロールは TextChanged イベントを発生させます。プロテクト ヘルパ ルーチン Raise<Property>Changed を使用して、このイベントを発生させることができます。しかし、ハッシュ テーブルの項目が追加されたときにプロパティ変更イベントを発生させても、そのオーバーヘッドに見合う効果は得られません。プロパティ変更イベントのヘルパ ルーチンを実装するコード例を次に示します。

Class Control
   Inherits Component
   Private m_text As String
   Public Property Text() As String
      Get
         Return m_text
      End Get
      Set
         If Not m_text.Equals(value) Then
            m_text = value
            RaiseTextChanged()
         End If
      End Set
   End Property
End Class
[C#]
class Control: Component
{
   string text;
   public string Text
   { 
      get
      { 
         return text; 
      }
      set
      {
         if (!text.Equals(value))
         {
            text = value;
            RaiseTextChanged();
         }
      }
   }
}

データ連結では、このパターンを使用して、プロパティの双方向の連結を実現しています。<Property>Changed イベントと Raise<Property>Changed イベントが発生しない場合には、データ連結は一方向にしか機能しませんが、データベースが変更されると、該当するプロパティも更新されます。この場合、<Property>Changed イベントを発生させる各プロパティが、それぞれがデータ連結をサポートしていることを示すメタデータを提供する必要があります。

外的要因の結果としてプロパティの値が変更された場合は、変更前イベントと変更後イベントを発生させることをお勧めします。これらのイベントによって、オブジェクトに対するメソッドの呼び出しではなく、ある操作の結果としてプロパティ値が変更されている、または変更されたことがわかります。

この典型的な例が、Edit コントロールの Text プロパティです。ユーザーがコントロールに情報を入力すると、プロパティ値が自動的に変更されます。このプロパティ値の変更が完了する前に、あるイベントを発生させます。このイベントは、変更前の値も変更後の値も渡しません。開発者は例外をスローすることによって、このイベントをキャンセルできます。このイベントの名前は、プロパティ名にサフィックス Changing を付けた名前にします。変更前イベントのコード例を次に示します。

Class Edit
   Inherits Control
   
   Public Property Text() As String
      Get
         Return m_text
      End Get
      Set
         If m_text <> value Then
            OnTextChanging(Event.Empty)
            m_text = value
         End If
      End Set
   End Property
End Class
[C#]
class Edit : Control 
{
   public string Text 
   { 
      get 
      { 
         return text; 
      }
      set 
      {
         if (text != value) 
         {
            OnTextChanging(Event.Empty);
            text = value;
         }
      }
   }
}

プロパティの値が変更された後にも、イベントが発生します。このイベントはキャンセルできません。このイベントの名前は、プロパティ名にサフィックス Changed を付けた名前にします。また、汎用的な PropertyChanged イベントも発生します。これら両方のイベントを発生させるパターンは、OnPropertyChanged メソッドから特定のイベントを発生させることです。OnPropertyChanged メソッドの使用方法を次のコード例に示します。

Class Edit
   Inherits Control  
   Public Property Text() As String
      Get
         Return m_text
      End Get
      Set
         If m_text <> value Then
            OnTextChanging(Event.Empty)
            m_text = value
            RaisePropertyChangedEvent(Edit.ClassInfo. m_text)
         End If
      End Set
   End Property
   Protected Sub OnPropertyChanged(e As PropertyChangedEventArgs)
      If e.PropertyChanged.Equals(Edit.ClassInfo. m_text) Then
         OnTextChanged(Event.Empty)
      End If
      If Not (onPropertyChangedHandler Is Nothing) Then
         onPropertyChangedHandler(Me, e)
      End If
   End Sub
End Class
[C#]
class Edit : Control 
{
   public string Text 
   {
      get 
      { 
         return text; 
      }
      set 
      {
         if (text != value) 
         {
            OnTextChanging(Event.Empty);
            text = value;
            RaisePropertyChangedEvent(Edit.ClassInfo.text);
         }
      }
   }

   protected void OnPropertyChanged(PropertyChangedEventArgs e) 
   {
      if (e.PropertyChanged.Equals(Edit.ClassInfo.text))
         OnTextChanged(Event.Empty);
      if (onPropertyChangedHandler != null)
         onPropertyChangedHandler(this, e);
   }
}

プロパティの基になる値がフィールドとして格納されない場合があり、そのような値の変更を追跡することは困難です。変更前イベントが発生したときに、プロパティ値を変更できるすべての箇所を検索し、イベントをキャンセルできるようにします。たとえば、前の Edit コントロールの例は、Text の値が実際にはウィンドウ ハンドル (HWND) に格納されるため、完全には正しくありません。この TextChanging イベントを発生させるには、実際には Windows メッセージを調べてテキストがいつ変更されたかを確認し、OnTextChanging で例外をスローしてイベントをキャンセルできるようにする必要があります。変更前イベントの作成が難しい場合は、変更後イベントだけをサポートしてもかまいません。

プロパティとメソッド

クラス ライブラリのデザイナは、クラスのメンバをプロパティとして実装するか、メソッドとして実装するかを決定する必要がある場合があります。一般的に、メソッドは操作を表し、プロパティはデータを表します。次のガイドラインを念頭に置くと、どちらを選択するか決定するときに役立ちます。

  • メンバが論理データ メンバの場合は、プロパティを使用します。次のメンバ宣言では、Name はクラスの論理メンバであるため、プロパティとして宣言されています。

    Public Property Name As String
       Get
          Return m_name
       End Get
       Set
          m_name = value
       End Set 
    End Property
    [C#]
    public string Name
       get 
       {
          return name;
       }
       set 
       {
          name = value;
       }
    
  • 次のような場合には、メソッドを使用します。

    • 操作が Object.ToString などの変換操作の場合。

    • ユーザーとやり取りし、その結果をキャッシュしておく必要があるような、負荷がかかる操作を行う場合。

    • get アクセサを使用してプロパティ値を取得すると、明らかに悪影響が発生する場合。

    • メンバを 2 回続けて呼び出した場合に、異なる結果が生じる場合。

    • 実行順序が重要となる場合。型のプロパティは、任意の順序で設定および取得できるようにします。

    • メンバが静的だが、変更可能な値を返す場合。

    • メンバが配列を返す場合。配列を返すプロパティは、混乱の原因になる可能性があります。通常は、内部の状態が変更されることがないように、内部配列のコピーを返す必要があります。このことは、このようなプロパティがインデックス付きプロパティだと見なされやすいという事実と併せて、非効率なコードを生み出す原因になります。次のコード例では、Methods プロパティが呼び出されるたびに、配列のコピーが作成されます。結果として、次のループの中で、2n+1 個の配列のコピーが作成されることになります。

      Dim type As Type = ' Get a type.
      Dim i As Integer
      For i = 0 To type.Methods.Length - 1 
         If type.Methods(i).Name.Equals("text") Then
            ' Perform some operation.
         End If 
      Next i
      [C#]
      Type type = // Get a type.
      for (int i = 0; i < type.Methods.Length; i++)
      {
         if (type.Methods[i].Name.Equals ("text"))
         {
            // Perform some operation.
         }
      }
      

プロパティとメソッドの適切な使用方法を次の例に示します。

Class Connection
   ' The following three members should be properties
   ' because they can be set in any order.   
   Property DNSName() As String
      ' Code for get and set accessors goes here.
   End Property
   Property UserName() As String
      ' Code for get and set accessors goes here.
   End Property
   Property Password() As String
      'Code for get and set accessors goes here.
   End Property
   ' The following member should be a method
   ' because the order of execution is important.
   ' This method cannot be executed until after the 
   ' properties have been set.   
   Function Execute() As Boolean
[C#]
class Connection
{
   // The following three members should be properties
   // because they can be set in any order.
   string DNSName {get{};set{};}
   string UserName {get{};set{};}
   string Password {get{};set{};}

   // The following member should be a method
   // because the order of execution is important.
   // This method cannot be executed until after the 
   // properties have been set.
   bool Execute ();
}

読み取り専用プロパティと書き込み専用プロパティ

プロパティの論理データ メンバが変更されないようにする場合は、読み取り専用プロパティを使用する必要があります。書き込み専用プロパティは使用しないでください。

インデックス付きプロパティの使用方法

メモ   インデックス付きプロパティは、インデクサとしても参照できます。

次の規則は、インデックス付きプロパティを使用するときのガイドラインを示しています。

  • インデックス付きプロパティは、プロパティの論理データ メンバが配列の場合に使用します。

  • インデックス付きプロパティには、整数値または文字列だけを使用するようにしてください。デザインでインデックス付きプロパティに他の型が必要な場合は、その型が論理データ メンバであるかどうかを再度確認します。論理データ メンバでない場合は、メソッドを使用します。

  • インデックスは 1 つだけ使用するようにしてください。デザインで複数のインデックスが必要な場合は、そのインデックスが論理データ メンバであるかどうかを再度確認します。論理データ メンバでない場合は、メソッドを使用します。

  • インデックス付きプロパティは各クラスで 1 つだけ使用し、このプロパティをそのクラスの既定のインデックス付きプロパティにします。C# プログラミング言語では、この規則はインデクサのサポートによって適用されます。

  • 既定で設定されている以外のインデックス付きプロパティは使用しないでください。C# では、このようなプロパティの使用は許可されていません。

  • インデックス付きプロパティの名前は Item にします。例としては、DataGrid.Item プロパティのトピックを参照してください。String クラスの Chars プロパティなど、よりわかりやすい名前がある場合以外は、この規則に従います。C# では、インデクサには常に Item という名前が付けられます。

  • オーバーロードされた複数のメソッドとセマンティクスが等しいインデックス付きプロパティやメソッドは提供しないでください。次のコード例では、Method プロパティを GetMethod(string) メソッドに変更する必要があります。C# では、このようなプロパティやメソッドの提供は許可されていません。

    ' Change the MethodInfo Type.Method property to a method.
    Property Type.Method(name As String) As MethodInfo
    Function Type.GetMethod(name As String, ignoreCase As Boolean) As MethodInfo
    [C#]
    // Change the MethodInfo Type.Method property to a method.
    MethodInfo Type.Method[string name]
    MethodInfo Type.GetMethod (string name, Boolean ignoreCase)
    [Visual Basic]
    ' The MethodInfo Type.Method property is changed to
    ' the MethodInfo Type.GetMethod method.
    Function Type.GetMethod(name As String) As MethodInfo
    Function Type.GetMethod(name As String, ignoreCase As Boolean) As MethodInfo
    [C#]
    // The MethodInfo Type.Method property is changed to
    // the MethodInfo Type.GetMethod method.
    MethodInfo Type.GetMethod(string name)
    MethodInfo Type.GetMethod (string name, Boolean ignoreCase) 
    

参照

クラス ライブラリ開発者向けのデザイン ガイドライン | プロパティの名前付けのガイドライン | クラス メンバの使用方法のガイドライン