public class Foo { public Color BackColor { } }
Defining a property with the same name as a type can cause ambiguity in some languages. It is best to avoid this ambiguity unless there is a clear justification for not doing so.
For example: System.WinForms.Form has an Icon property even though there is an Icon class because Form.Icon is so much easier to understand than Form.FormIcon or Form.DisplayIcon etc.
However, System.WinForms.UI.Control has a color property. Because there is a Color class, the Color property was named BackgroundColor as it’s a more meaningful name that does not conflict.
It is often times the case that a particular feature of an object will not take effect until the developer has specified a particular set of properties, or until an object has a particular state. Until the object is in the correct state, the feature is not active. Once in the right state, then the feature automatically activates itself without requiring an explicit call. The semantics are be the same no matter what order the developer sets the property values or how the developer get's the object into the active state.
For example, a TextBox control may have two related properties: DataSource and DataField. DataSource specifies the table name, and DataField specifies the the column name. Once both are specified, the control can automatically bind data from the table into the Text property of the control. However, the developer is able to set the properties in any order, for example:
TextBox t = new TextBox(); t.DataSource = "Publishers"; t.DataField = "AuthorID"; // data binding feature now active
The following is equivalent:
TextBox t = new TextBox(); t.DataField = "AuthorID"; t.DataSource = "Publishers"; // data binding feature now active
In addition, the developer can set the properties to null, meaning 'not specified':
TextBox t = new TextBox(); t.DataField = "AuthorID"; t.DataSource = "Publishers"; // data binding feature now active t.DataSource = null; // data binding feature now inactive
Implement this by tracking the state of the data binding feature and automatically activating or deactivating it at the appropriate times. For example:
public class TextBox { string dataSource; string dataField; boolean active; public string DataSource { get { return dataSource; } set { if (value != dataSource) { // set the property value first, in case activate fails dataSource = value; // update active state SetActive(dataSource != null && dataField != null); } } } public string DataField { get { return dataField; } set { if (value != dataField) { // set the property value first, in case activate fails dataField = value; // update active state SetActive(dataSource != null && dataField != null); } } } void SetActive(boolean value) { if (value != active) { if (value) { Activate(); Text = dataBase.Value(dataField); } else { Deactivate(); Text = ""; } active = value; // set active only if success } } void Activate() { // open database } void Deactivate() { // close database } }
In the previous example, the following expression determines whether or not the object is in a state in which the data binding feature can activate itself:
dataSource != null && dataField != null
It may make sense to make this into a method which will determine whether the object can be activated given it's current state, and then activate as necessary.
void UpdateActive { SetActive(dataSource != null && dataField != null); }
The expression that appears in this method is an indicator as to what parts of the object model that needs to be examined in order to enforce these state transitions. In this case, the DataSource and DataField properties are affected.
If there is a good scenario for a user wanting to know when a property of another component is changing programmatically, then the component should raise a PropertyChanged event for the property. For example, a Control may raise a PropertyChanged event when the Control's position changes. This would allow a tooltip provider Control to respond to the change in position and update the location of the tooltip window. The PropertyChanged event is raised after the property value has changed. There is a protected helper routine RaisePropertyChanged which is used to raise this event. However, it is unlikely to be worth the overhead to raise a PropertyChanged event for a Hashtable item addition.
class Control : Component { Rect position; [Bindable(True)] public Rect Position { get { return position; } set { if (!position.Equals(value)) { position = value; RaisePropertyChangedEvent(“position”); } } } }
Data binding uses this pattern to allow two way binding of the property. If these events do not exist, then data binding works in one direction (if the database changes, the property is updated). Each property that raises the PropertyChanged event should provide metadata to indicate that the property supports data binding.
If a property value changes via some external force (e.g. not by calling methods on the object), then raise events telling the developer that the value is changing and has changed. An good example is the Text property of a Edit control. When the user types, the property value automatically changes.
An event is raised before the value of the property has changed. It does not pass the old or new value, but can be canceled by the developer by throwing an exception. The name of the event is the name of the property followed by Changing. For example:
class Edit : Control { public string Text { get { return text; } set { if (text != value) { OnTextChanging(Event.Empty); text = value; } } } }
An event is also raised after the value of the property has changed. It can not be cancelled. The name of the event is the name of the property followed by Changed. The generic PropertyChanged event should also be raised (see previous section on PropertyChanged events). The pattern for raising both of these is to raise the specific event from the OnPropertyChanged method. For example:
class Edit : Control { public string Text { [Bindable(True)] public string Text { get { return text; } set { if (text != value) { OnTextChanging(Event.Empty); text = value; RaisePropertyChangedEvent(Edit.ClassInfo.text); } } } protected void OnPropertyChanged(PropertyChangedEvent e) { if (e.PropertyChanged.Equals(Edit.ClassInfo.text)) OnTextChanged(Event.Empty); if (onPropertyChangedHandler != null) onPropertyChangedHandler(this, e); } } }
There are cases when the underlying property value is not stored as a field and thus it's value is more difficult to track changes to. When raising the changing event, find all the places that the property value can change and provide the ability to cancel. For example, the previous Edit control example is not actually accurate because the Text value is actually stored in the HWND. In order to raise the TextChanging event, we must examine Windows messages for when the text may change, and allow for an exception thrown in OnTextChanging to cancel. If it is too difficult to provide a changing event, it's reasonable to just support Changed.
Designers sometimes face a decision between a property and a method. Guidelines for choosing between these options:
Do use a Property:
For example:
string Type.Name //should be a property, string Name because the name logical attribute of a //class but: Guid Guid.GetNext() //should not be a property, because a Guids don’t naturally have a “next” Guid.
Do use a Method:
class Connection { //It’s good for these three to be properites as they can be set in any order String DNSName {get{};set{};} String UserName {get{};set{};} String Password {get{};set{};} //Howver, it would be bad for this to be a properity because it has to be done in a particular order //(namly after the others have been set). We use a method here. bool Execute (); }
Properties that return arrays can be very misleading. Usually it is necessary to return a copy of the internal array so that the user cannot change internal state. This, coupled with the fact that a user could easily assume it's an indexed property, leads to really inefficient code such as:
Type type = //get a type somehow for (int i = 0; i < type.Methods.Length; i++) { if (type.Methods[i].Name.Equals ("foo")) {...} }
In this code example, each call to the Methods property creates a copy of the array. That would be n+1 copies for this loop.
MethodInfo Type.Method[string name] MethodInfo Type.GetMethod (string name, boolean ignoreCase)