Automatically implemented properties 


Мы поможем в написании ваших работ!



ЗНАЕТЕ ЛИ ВЫ?

Automatically implemented properties



When a property is specified as an automatically implemented property, a hidden backing field is automatically available for the property, and the accessors are implemented to read from and write to that backing field.

The following example:

public class Point {
public int X { get; set; } // automatically implemented
public int Y { get; set; } // automatically implemented
}

is equivalent to the following declaration:

public class Point {
private int x;
private int y;
public int X { get { return x; } set { x = value; } }
public int Y { get { return y; } set { y = value; } }
}

Because the backing field is inaccessible, it can be read and written only through the property accessors, even within the containing type. This means that automatically implemented read-only or write-only properties do not make sense, and are disallowed. It is however possible to set the access level of each accessor differently. Thus, the effect of a read-only property with a private backing field can be mimicked like this:

public class ReadOnlyPoint {
public int X { get; private set; }
public int Y { get; private set; }
public ReadOnlyPoint(int x, int y) { X = x; Y = y; }
}

This restriction also means that definite assignment of struct types with auto-implemented properties can only be achieved using the standard constructor of the struct, since assigning to the property itself requires the struct to be definitely assigned. This means that user-defined constructors must call the default constructor.

Accessibility

If an accessor has an accessor-modifier, the accessibility domain (§3.5.2) of the accessor is determined using the declared accessibility of the accessor-modifier. If an accessor does not have an accessor-modifier, the accessibility domain of the accessor is determined from the declared accessibility of the property or indexer.

The presence of an accessor-modifier never affects member lookup (§7.3) or overload resolution (§7.5.3). The modifiers on the property or indexer always determine which property or indexer is bound to, regardless of the context of the access.

Once a particular property or indexer has been selected, the accessibility domains of the specific accessors involved are used to determine if that usage is valid:

· If the usage is as a value (§7.1.1), the get accessor must exist and be accessible.

· If the usage is as the target of a simple assignment (§7.17.1), the set accessor must exist and be accessible.

· If the usage is as the target of compound assignment (§7.17.2), or as the target of the ++ or -- operators (§7.5.9, §7.6.5), both the get accessors and the set accessor must exist and be accessible.

In the following example, the property A.Text is hidden by the property B.Text, even in contexts where only the set accessor is called. In contrast, the property B.Count is not accessible to class M, so the accessible property A.Count is used instead.

class A
{
public string Text {
get { return "hello"; }
set { }
}

public int Count {
get { return 5; }
set { }
}
}

class B: A
{
private string text = "goodbye";
private int count = 0;

new public string Text {
get { return text; }
protected set { text = value; }
}

new protected int Count {
get { return count; }
set { count = value; }
}
}

class M
{
static void Main() {
B b = new B();
b.Count = 12; // Calls A.Count set accessor
int i = b.Count; // Calls A.Count get accessor
b.Text = "howdy"; // Error, B.Text set accessor not accessible
string s = b.Text; // Calls B.Text get accessor
}
}

An accessor that is used to implement an interface may not have an accessor-modifier. If only one accessor is used to implement an interface, the other accessor may be declared with an accessor-modifier:

public interface I
{
string Prop { get; }
}

public class C: I
{
public Prop {
get { return "April"; } // Must not have a modifier here
internal set {...} // Ok, because I.Prop has no set accessor
}
}

Virtual, sealed, override, and abstract accessors

A virtual property declaration specifies that the accessors of the property are virtual. The virtual modifier applies to both accessors of a read-write property—it is not possible for only one accessor of a read-write property to be virtual.

An abstract property declaration specifies that the accessors of the property are virtual, but does not provide an actual implementation of the accessors. Instead, non-abstract derived classes are required to provide their own implementation for the accessors by overriding the property. Because an accessor for an abstract property declaration provides no actual implementation, its accessor-body simply consists of a semicolon.

A property declaration that includes both the abstract and override modifiers specifies that the property is abstract and overrides a base property. The accessors of such a property are also abstract.

Abstract property declarations are only permitted in abstract classes (§10.1.1.1).The accessors of an inherited virtual property can be overridden in a derived class by including a property declaration that specifies an override directive. This is known as an overriding property declaration. An overriding property declaration does not declare a new property. Instead, it simply specializes the implementations of the accessors of an existing virtual property.

An overriding property declaration must specify the exact same accessibility modifiers, type, and name as the inherited property. If the inherited property has only a single accessor (i.e., if the inherited property is read-only or write-only), the overriding property must include only that accessor. If the inherited property includes both accessors (i.e., if the inherited property is read-write), the overriding property can include either a single accessor or both accessors.

An overriding property declaration may include the sealed modifier. Use of this modifier prevents a derived class from further overriding the property. The accessors of a sealed property are also sealed.

Except for differences in declaration and invocation syntax, virtual, sealed, override, and abstract accessors behave exactly like virtual, sealed, override and abstract methods. Specifically, the rules described in §10.6.3, §10.6.4, §10.6.5, and §10.6.6 apply as if accessors were methods of a corresponding form:

· A get accessor corresponds to a parameterless method with a return value of the property type and the same modifiers as the containing property.

· A set accessor corresponds to a method with a single value parameter of the property type, a void return type, and the same modifiers as the containing property.

In the example

abstract class A
{
int y;

public virtual int X {
get { return 0; }
}

public virtual int Y {
get { return y; }
set { y = value; }
}

public abstract int Z { get; set; }
}

X is a virtual read-only property, Y is a virtual read-write property, and Z is an abstract read-write property. Because Z is abstract, the containing class A must also be declared abstract.

A class that derives from A is show below:

class B: A
{
int z;

public override int X {
get { return base.X + 1; }
}

public override int Y {
set { base.Y = value < 0? 0: value; }
}

public override int Z {
get { return z; }
set { z = value; }
}
}

Here, the declarations of X, Y, and Z are overriding property declarations. Each property declaration exactly matches the accessibility modifiers, type, and name of the corresponding inherited property. The get accessor of X and the set accessor of Y use the base keyword to access the inherited accessors. The declaration of Z overrides both abstract accessors—thus, there are no outstanding abstract function members in B, and B is permitted to be a non-abstract class.

When a property is declared as an override, any overridden accessors must be accessible to the overriding code. In addition, the declared accessibility of both the property or indexer itself, and of the accessors, must match that of the overridden member and accessors. For example:

public class B
{
public virtual int P {
protected set {...}
get {...}
}
}

public class D: B
{
public override int P {
protected set {...} // Must specify protected here
get {...} // Must not have a modifier here
}
}

Events

An event is a member that enables an object or class to provide notifications. Clients can attach executable code for events by supplying event handlers.

Events are declared using event-declarations:

event-declaration:
attributesopt event-modifiersopt event type variable-declarators;
attributesopt event-modifiersopt event type member-name { event-accessor-declarations }

event-modifiers:
event-modifier
event-modifiers event-modifier

event-modifier:
new
public
protected
internal
private
static
virtual
sealed
override
abstract
extern

event-accessor-declarations:
add-accessor-declaration remove-accessor-declaration
remove-accessor-declaration add-accessor-declaration

add-accessor-declaration:
attributesopt add block

remove-accessor-declaration:
attributesopt remove block

An event-declaration may include a set of attributes (§17) and a valid combination of the four access modifiers (§10.3.5), the new (§10.3.4), static (§10.6.2), virtual (§10.6.3), override (§10.6.4), sealed (§10.6.5), abstract (§10.6.6), and extern (§10.6.7) modifiers.

Event declarations are subject to the same rules as method declarations (§10.6) with regard to valid combinations of modifiers.

The type of an event declaration must be a delegate-type (§4.2), and that delegate-type must be at least as accessible as the event itself (§3.5.4).

An event declaration may include event-accessor-declarations. However, if it does not, for non-extern, non-abstract events, the compiler supplies them automatically (§10.8.1); for extern events, the accessors are provided externally.

An event declaration that omits event-accessor-declarations defines one or more events—one for each of the variable-declarators. The attributes and modifiers apply to all of the members declared by such an event-declaration.

It is a compile-time error for an event-declaration to include both the abstract modifier and brace-delimited event-accessor-declarations.

When an event declaration includes an extern modifier, the event is said to be an external event. Because an external event declaration provides no actual implementation, it is an error for it to include both the extern modifier and event-accessor-declarations.

It is a compile-time error for a variable-declarator of an event declaration with an abstract or external modifier to include a variable-initializer.

An event can be used as the left-hand operand of the += and -= operators (§7.17.3). These operators are used, respectively, to attach event handlers to or to remove event handlers from an event, and the access modifiers of the event control the contexts in which such operations are permitted.

Since += and -= are the only operations that are permitted on an event outside the type that declares the event, external code can add and remove handlers for an event, but cannot in any other way obtain or modify the underlying list of event handlers.

In an operation of the form x += y or x -= y, when x is an event and the reference takes place outside the type that contains the declaration of x, the result of the operation has type void (as opposed to having the type of x, with the value of x after the assignment). This rule prohibits external code from indirectly examining the underlying delegate of an event.

The following example shows how event handlers are attached to instances of the Button class:

public delegate void EventHandler(object sender, EventArgs e);

public class Button: Control
{
public event EventHandler Click;
}

public class LoginDialog: Form
{
Button OkButton;
Button CancelButton;

public LoginDialog() {
OkButton = new Button(...);
OkButton.Click += new EventHandler(OkButtonClick);
CancelButton = new Button(...);
CancelButton.Click += new EventHandler(CancelButtonClick);
}

void OkButtonClick(object sender, EventArgs e) {
// Handle OkButton.Click event
}

void CancelButtonClick(object sender, EventArgs e) {
// Handle CancelButton.Click event
}
}

Here, the LoginDialog instance constructor creates two Button instances and attaches event handlers to the Click events.

Field-like events

Within the program text of the class or struct that contains the declaration of an event, certain events can be used like fields. To be used in this way, an event must not be abstract or extern, and must not explicitly include event-accessor-declarations. Such an event can be used in any context that permits a field. The field contains a delegate (§15) which refers to the list of event handlers that have been added to the event. If no event handlers have been added, the field contains null.

In the example

public delegate void EventHandler(object sender, EventArgs e);

public class Button: Control
{
public event EventHandler Click;

protected void OnClick(EventArgs e) {
if (Click!= null) Click(this, e);
}

public void Reset() {
Click = null;
}
}

Click is used as a field within the Button class. As the example demonstrates, the field can be examined, modified, and used in delegate invocation expressions. The OnClick method in the Button class “raises” the Click event. The notion of raising an event is precisely equivalent to invoking the delegate represented by the event—thus, there are no special language constructs for raising events. Note that the delegate invocation is preceded by a check that ensures the delegate is non-null.

Outside the declaration of the Button class, the Click member can only be used on the left-hand side of the += and –= operators, as in

b.Click += new EventHandler(…);

which appends a delegate to the invocation list of the Click event, and

b.Click –= new EventHandler(…);

which removes a delegate from the invocation list of the Click event.

When compiling a field-like event, the compiler automatically creates storage to hold the delegate, and creates accessors for the event that add or remove event handlers to the delegate field. The addition and removal operations are thread safe, and may (but are not required to) be done while holding the lock (§8.12) on the containing object for an instance event, or the type object (§7.6.10.6) for a static event.

Thus, an instance event declaration of the form:

class X
{
public event D Ev;
}

will be compiled to something equivalent to:

class X
{
private D __Ev; // field to hold the delegate

public event D Ev {
add {
/* add the delegate in a thread safe way */
}

remove {
/* remove the delegate in a thread safe way */
}
}
}

Within the class X, references to Ev on the left-hand side of the += and –= operators cause the add and remove accessors to be invoked. All other references to Ev are compiled to reference the hidden field __Ev instead (§7.6.4). The name “__Ev” is arbitrary; the hidden field could have any name or no name at all.

Event accessors

Event declarations typically omit event-accessor-declarations, as in the Button example above. One situation for doing so involves the case in which the storage cost of one field per event is not acceptable. In such cases, a class can include event-accessor-declarations and use a private mechanism for storing the list of event handlers.

The event-accessor-declarations of an event specify the executable statements associated with adding and removing event handlers.

The accessor declarations consist of an add-accessor-declaration and a remove-accessor-declaration. Each accessor declaration consists of the token add or remove followed by a block. The block associated with an add-accessor-declaration specifies the statements to execute when an event handler is added, and the block associated with a remove-accessor-declaration specifies the statements to execute when an event handler is removed.

Each add-accessor-declaration and remove-accessor-declaration corresponds to a method with a single value parameter of the event type and a void return type. The implicit parameter of an event accessor is named value. When an event is used in an event assignment, the appropriate event accessor is used. Specifically, if the assignment operator is += then the add accessor is used, and if the assignment operator is -= then the remove accessor is used. In either case, the right-hand operand of the assignment operator is used as the argument to the event accessor. The block of an add-accessor-declaration or a remove-accessor-declaration must conform to the rules for void methods described in §10.6.10. In particular, return statements in such a block are not permitted to specify an expression.

Since an event accessor implicitly has a parameter named value, it is a compile-time error for a local variable or constant declared in an event accessor to have that name.

In the example

class Control: Component
{
// Unique keys for events
static readonly object mouseDownEventKey = new object();
static readonly object mouseUpEventKey = new object();

// Return event handler associated with key
protected Delegate GetEventHandler(object key) {...}

// Add event handler associated with key
protected void AddEventHandler(object key, Delegate handler) {...}

// Remove event handler associated with key
protected void RemoveEventHandler(object key, Delegate handler) {...}

// MouseDown event
public event MouseEventHandler MouseDown {
add { AddEventHandler(mouseDownEventKey, value); }
remove { RemoveEventHandler(mouseDownEventKey, value); }
}

// MouseUp event
public event MouseEventHandler MouseUp {
add { AddEventHandler(mouseUpEventKey, value); }
remove { RemoveEventHandler(mouseUpEventKey, value); }
}

// Invoke the MouseUp event
protected void OnMouseUp(MouseEventArgs args) {
MouseEventHandler handler;
handler = (MouseEventHandler)GetEventHandler(mouseUpEventKey);
if (handler!= null)
handler(this, args);
}
}

the Control class implements an internal storage mechanism for events. The AddEventHandler method associates a delegate value with a key, the GetEventHandler method returns the delegate currently associated with a key, and the RemoveEventHandler method removes a delegate as an event handler for the specified event. Presumably, the underlying storage mechanism is designed such that there is no cost for associating a null delegate value with a key, and thus unhandled events consume no storage.

Static and instance events

When an event declaration includes a static modifier, the event is said to be a static event. When no static modifier is present, the event is said to be an instance event.

A static event is not associated with a specific instance, and it is a compile-time error to refer to this in the accessors of a static event.

An instance event is associated with a given instance of a class, and this instance can be accessed as this (§7.6.7) in the accessors of that event.

When an event is referenced in a member-access (§7.6.4) of the form E.M, if M is a static event, E must denote a type containing M, and if M is an instance event, E must denote an instance of a type containing M.

The differences between static and instance members are discussed further in §10.3.7.



Поделиться:


Последнее изменение этой страницы: 2016-08-10; просмотров: 204; Нарушение авторского права страницы; Мы поможем в написании вашей работы!

infopedia.su Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав. Обратная связь - 18.118.120.109 (0.034 с.)