确定是属性或是方法更适合于您的需要。有关在属性和方法之间选择的详细信息,请参见属性与方法。
根据建议的属性命名指南选择属性名称。
在使用 set 访问器访问属性时,请在更改属性前保留属性的值。这将确保在 set 访问器引发异常时不会丢失数据。
属性状态问题
允许以任何顺序设置属性。对于其他属性,属性应当是无状态的。经常出现的情况是,对象的某个特定功能直到开发人员指定一组特定的属性或者直到对象具有某个特定状态时才生效。除非对象处于正确的状态中,否则功能就不是活动的。当对象处于正确的状态中时,功能将自动激活它本身而不需要显式调用。无论开发人员以何种顺序设置属性值,也不管他们如何使对象进入活动状态中,语义都是一样的。
例如,TextBox 控件可能有两个相关属性:DataSource 和 DataField。DataSource 指定表名称,DataField 指定列名称。两个属性一经指定,控件便可以自动地将数据从表绑定到控件的 Text 属性中。下面的代码示例阐释可以按任意顺序设置的属性。
[Visual Basic]
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 属性。因此,上面的代码和下面的代码是等效的。
[Visual Basic]
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.
还可以将属性设置为空(Visual Basic 中为 Nothing)以指示未指定值。
[Visual Basic]
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.
下面的代码示例阐释如何跟踪数据绑定功能的状态,以及如何在适当的时候自动激活或停用它。
[Visual Basic]
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.
}
}
上述示例中的以下表达式确定对象是否处于数据绑定功能可以激活其自身的状态中。
[Visual Basic]
(Not (value Is Nothing) And Not (m_dataField Is Nothing))
[C#]
value != null && dataField != null
可以使激活自动化,方法是创建一个方法,此方法在给定了对象的当前状态时确定是否可以激活该对象,然后根据需要激活它。
[Visual Basic]
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);
}
如果您确实有相关属性(如 DataSource 和 DataMember),则应考虑实现 ISupportInitialize 接口 。这将允许设计者(或用户)在设置多个属性时调用 ISupportInitialize.BeginInit 和 ISupportInitialize.EndInit 方法,以使组件可以提供优化。在上面的示例中,ISupportInitialize 可以防止在安装成功完成之前不必要地尝试访问数据库。
此方法中出现的表达式指示需要检查以强制这些状态转换的对象模型的部分。在此情况下,DataSource 和 DataField 属性将受到影响。有关在属性和方法之间进行选择的更多信息,请参见属性与方法。
引发属性已更改事件
如果组件要在它们的属性以编程方式发生更改时通知使用者,这些组件将引发属性已更改事件。属性已更改事件的命名规则是将 Changed
后缀添加到属性名称,如 TextChanged
。例如,控件在其文本属性发生更改时,可能会引发 TextChanged 事件。可以使用受保护的帮助器例程 Raise<Property>Changed 来引发该事件。但是,花费系统开销来为添加的哈希表项引发属性已更改事件可能不是很值得。以下代码示例阐释在发生属性已更改事件时帮助器例程的实现。
[Visual Basic]
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 事件的属性都应当提供元数据以指示该属性支持数据绑定。
如果属性的值由于外部强制而更改,建议您引发 changing/changed 事件。这些事件向开发人员指示属性的值由于操作而不是通过调用对象上的方法正在更改或者已经更改。
Edit 控件的 Text 属性是一个很好的例子。当用户将信息键入到控件中时,属性值将自动更改。事件在属性的值发生更改之前引发。它不传递旧值或新值,开发人员可以通过引发异常来取消该事件。该事件的名称由属性名后跟后缀 Changing
组成。下面的代码示例阐释 changing 事件。
[Visual Basic]
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 方法的使用。
[Visual Basic]
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);
}
}
存在属性的基础值不是作为字段存储的情况,这使得难以跟踪值的更改。当引发 changing 事件时,查找属性值可以更改的所有位置并提供取消事件的能力。例如,上一个 Edit 控件示例不完全精确,原因是 Text 值实际上存储在窗口句柄 (HWND) 中。若要引发 TextChanging 事件,您必须检查 Windows 消息以确定文本何时可能更改,并允许在 OnTextChanging 中引发异常以取消该事件。如果因为太困难而无法提供 changing 事件,仅支持 changed 事件是合理的。
属性与方法
类库设计人员经常必须决定是将类成员作为属性还是方法来实现。通常,方法代表操作而属性代表数据。请使用以下指南来帮助您在这些选项之间进行选择。
- 当成员为逻辑数据成员时,使用属性。在下列成员声明中,
Name
是属性,原因是它是类的逻辑成员。[Visual Basic] 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 访问器获取属性值将产生可观察到的副作用。
- 连续调用两次成员会产生不同的结果。
- 执行的顺序很重要。请注意,应当能够按任何顺序设置和检索类型的属性。
- 成员是静态的,但是返回可以更改的值。
- 成员返回数组。返回数组的属性可能非常容易令人产生误解。通常有必要返回内部数组的副本以使用户无法更改内部状态。这种情况再加上用户会很容易地假定它是索引属性,将导致低效的代码。在下面的代码示例中,对
Methods
属性的每个调用都创建该数组的一个副本。结果,在下面的循环中将创建该数组的 2n+1 个副本。[Visual Basic] 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. } }
下面的示例阐释属性和方法的正确用法。
[Visual Basic]
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 ();
}
只读和只写属性
当用户不能更改属性的逻辑数据成员时,应当使用只读属性。不要使用只写属性。
索引属性的使用
注意 索引属性又称索引器。
以下规则概述使用索引属性的指南:
- 当属性的逻辑数据成员为数组时,请使用索引属性。
- 考虑对索引属性只使用整数值或字符串。如果设计需要对索引属性使用其他类型,请重新考虑其是否代表逻辑数据成员。否则,请使用方法。
- 考虑只使用一个索引。如果设计需要使用多个索引,请重新考虑其是否代表逻辑数据成员。否则,请使用方法。
- 仅为每个类使用一个索引属性,并使其成为该类的默认索引属性。此规则是通过 C# 编程语言中的索引器支持来强制使用的。
- 不要使用非默认的索引属性。C# 不允许此类情况。
- 将索引属性命名为
Item
。有关示例,请参见 DataGrid.Item 属性 。请遵循此规则,除非存在对于用户来说更直观的名称,如 String 类的Chars
属性。在 C# 中,索引器总是命名为Item
。 - 不要提供在语义上等效于两个或更多个重载方法的索引属性和方法。在下面的代码示例中,
Method
属性将被更改为 GetMethod(string) 方法。注意,C# 不允许此类情况。[Visual Basic] ' 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)
请参见
类库开发人员设计指南 | 属性命名指南 | 类成员使用指南