C# Basics
Short Description
Download C# Basics...
Description
C# MODIFIERS
SUMMARY • • • • • • • • • • • •
Introduction Access Modifiers abstract const extern override partial (C# 2.0) readonly sealed unsafe virtual volatile
INTRODUCTION Modifiers are C# keywords used to modify declarations of types (class, struct, interface, enum) and type members (fields, properties, methods, indexers, ...). The remaining sections explain these modifiers.
ACCESS MODIFIERS Access modifiers are keywords used to specify the declared accessibility of types and type members. The four access modifiers are discussed in the table below: Access Modifier
public
Meaning public is an access modifier for types and type members. This is the most permissive access level as there are no restrictions on accessing a public type or type member. internal is an access modifier for types and type members. internal members are accessible only within file of the same assembly. It is an error to reference an internal type or internal type member outside the assembly within which it was declared.
internal
A common use of internal access is in component-based development because it enables a group of components to interact in a private matter without being exposed to the outer world. For example, a Data Access Layer could have several classes with internal members that are only used by the DAL.
protected is an access modifier for type members only. A protected member is protected only accessible within the body of the containing type and from within any classes derived from the containing type. private private is an access modifier for type
members only. private access is the least accessible and such members are only accessible within the body of the containing type. Note that nested types within the same containing body can also access those private members.
Accessibility Levels The four access modifiers listed above result in five accessibility levels shown below. Note how certain accessibility levels are not permitted depending on the context in which an access modifiers was used: Accessibility Level
Applies to namespaces
Meaning
public
Access is not restricted.
internal
Yes
Applies to Types
Applies to Type members
Yes
Yes
Access is limited to the current assembly (project). No
Yes
Yes
protected
Access is limited to the containing class, and classes derived from the containing class
No
No
Yes
internal protected
Access is limited to the current assembly (project), No the containing class, and classes derived from the containing class
No
Yes
private
Access is limited to the containing type.
No
Yes
No
The following table illustrates accessibility levels for namespaces, types, and type members. Note that namespace elements (i.e., classes, structs, interfaces and enum) can only have public or internal declared accessibility. Access modifiers private, protected, and protected internal are not allowed on namespace members.
Default Context Accessibility namespace public class
None
Internal
public internal
private
public protected internal internal protected private
public internal
public
None
public internal
private
public internal private
pubic
None
internal (if top public internal level)
interface internal (if top level) struct internal (if top level) enum
Allowed Allowed Default Declared Declared Member Accessibility Accessibility Accessibility On Members
internal (if top public internal level)
ABSTRACT Applies To: Classes, methods, and properties. The meaning of the keyword abstract depends on the context in which it is used. The abstract keyword can be used in three contexts:
• • •
classes methods properties
Abstract Classes An abstract class means that the class is intended to be used as a base class only. Note the following features of abstract classes: • • • •
An abstract class cannot be instantiated. An abstract class may contain abstract methods and properties. A non-abstract class that derives from an abstract class must implement all inherited abstract methods and properties. (if any). An abstract class must provide implementation for all interface members.
public abstract class Person { ... }
Abstract Methods and Properties An abstract method or property means that the method (or property) does not have an implementation. Note the following features of abstract methods and properties: • • • • •
An abstract method/property is implicitly virtual. Abstract method/property declarations are only permitted in abstract classes. An abstract method/property has no implementation body. The implementation of an abstract method/property must be provided by an overriding method that is a member of class deriving from the abstract class containing the abstract method. It is an error to use static, virtrual, or override in an abstract method declaration.
The following class illustrates the above points: public interface IFile { void Open(); void Close(); } public abstract class Folder : IFile { // Data members long lSize; // Interface members must be implemented, even if the containing class is abstract public void Open() { Trace.WriteLine( "IFile.Open" ); } public void Close() { Trace.WriteLine( "IFile.Close" ); } // Abstract members and properties public abstract void RenameFolder(); public abstract string FolderName { get; set; } // Non-abstract methods/properties public long FolderSize
{ get { return 0; } set { lSize = value; } } } public class WindowsFolder : Folder { // Data members string strFolderName; // Implement inherited abstract methods public override void RenameFolder() { Trace.WriteLine( "WindowsFolder.RenameFolder" ); } public override string FolderName { get { return strFolderName; } set { strFolderName = value; } } }
CONST Applies To: fields and member variables. The keyword const is used to modify the declaration of a field or local variables. It indicates that the value of the field or local variable cannot be modified. Note the following two points for using const: • • •
The constant expression used to initialize the constant must yield a value of the target type, or a value of a type that can be implicitly converted to the target type. The constant expression used to initialize the constant must be one that can be fully evaluated at compile time. Therefore, the only allowable value for a reference type is either a string or null. static is not allowed on a constant expression.
public class ClassWithConstants { // const is applied to fields. private const double PI = 3.14; // A const private const double CM_To_Inch = 2.54; // A const private const double Inch_To_CM = 1 / CM_To_Inch; // A const can participate in a const expression private const string USER = null; // reference types must be initialized either to null or to a string public void ConvertFromEnglishToMetric() { // const can also be applied to local variables const double Meter_To_Inch = CM_To_Inch * 100; ... } }
EXTERN
Applies To: Method, property, event, indexer, operator, constructor, and destructor The extern modifier is used in a class member declaration (method, property, event, indexer, operator, constructor, and destructor ) to indicate that the method is implemented somewhere else outside the C# code (externally). extern is usually used with the DllImport attribute. And because an extern method should not provide an implementation, there is no method body. For example: public class MyClass { [DllImport("User32.dll")] pblic static extern GetDirectoryName( out string strDirName ); public void ShowDirectoryName() { string sName; GetDirectoryName( out sName ); method just like any other C# method return sName; } }
// Use the extern
See External Methods in Classes section. It is an error to use extern and abstract modifiers to modify the same member. abstract modifier means that the method implementation is not provided in the containing class but should be provided in derived class, whereas extern means that the method is implemented outside the C# code.
External Assembly Alias (New in C# 2.0) Sometimes you may have to use two or more versions of the same assembly in the same application. In other words, you need to reference two versions of the same assembly that have the same fully-qualified name. By using an external assembly alias, the namespaces of each assembly can be wrapped inside root-level namespaces named by the alias, allowing them to be used in the same file. External assembly aliases are accomplished with the alias sub-option of the /Reference compiler option. The main procedure is as follows: Suppose you have a Data Access Layer project called DAL written in VB. You compile this project to produce DAL_VB.DLL. This DLL is in turn used by a business objects layer DLL called BOL.DLL. You then rewrite DAL in C# but only manage to partially finish it. This partially finished code is now in a DLL called DAL_CS.DLL. Both DAL_VB.DLL and DAL_CS.DLL refer to the code base which has the same namespaces. In your BOL.DLL you still would like to reference both DLLs - DAL_CS.DLL for the new and improved functionality, and DAL_VB.DLL for the remaining yet-to-translate functionality. You then compile your BOL.DLL with the /Reference option as follows: /Reference DALVB=DAL_VB.DLL /Reference DALCS=DAL_CS.DLL This will setup two external references called DALVB and DALCS, which you can use in the BOL code via an extern statement as follows: extern DALVB; extern DALCS; // You can now refer to the same class from both DLLs DALVB.DAL.ExecuteNonQuery( ... ); // Old DLL DALCS.DAL.ExecuteNonQuery( ... ); // New DLL
OVERRIDE Applies To : Method, property, event, indexer. An override class member provides a new implementation for a member derived from a base class. Note the following points: • • •
The overridden method in the derived class must have the same exact signature as the inherited class member. The inherited class member to be overridden must be abstract, virtual, or override. You cannot override a static or non-virtual method.
See Virtual Methods in Classes section for a full example.
PARTIAL (C# 2.0) Applies to: classes, structs and interfaces. Does not apply to delegate and enums. partial keyword is used to split the definition of a class or struct or interface across multiple source files. Each source file would contain a section of the class definition and all parts are combined when the application is compiled. The following example illustrates: // File1.cs public partial class Order { private GetOrderDetails() { ... } } // File2.cs public partial class Order { public RetrieveOrder() { GetOrderDetails(); ... } } Using partial is desirable in the following situations. • •
In large projects spreading a class definition over multiple source files allows multiple programmers to work on it simulataneously. When working with automatically generated source code, your code can be added to the automatically generated source code (in a separate class using partial keyword) without having to edit the automatically generated source code. Visual Studio uses this functionality when creating Windows Forms (and other file types). Consider this code generated by VS.NET 2005 for a Windows Forms class:
// MyForm.cs. Your code goes into this class public partial class MyForm : Form { public MyForm() { InitializeComponent(); } // Your code would be added here ... }
// MyForm.Designer.cs. Automatically generated code maintained by VS.NET as you add/remove controls partial class MyForm { private System.ComponentModel.IContainer components = null; protected override void Dispose(bool disposing) { if (disposing && (components != null)) { components.Dispose(); } base.Dispose(disposing); } #region Windows Form Designer generated code ... #endregion } Note the following points about the partial keyword: •
• • • • • •
All the parts of a partial definition must use the partial keyword, and all the parts must be available at compile time to form the final type. Obviously, all parts must have the same declared accessibility ( a partial part cannot be public while another is internal). All parts of a partial definition must be defined in the same assembly. If any of the parts is declared abstract, then the entire type is abstract. Likewise, if any of the parts is declared sealed, then the entire type is sealed. If any of the parts inherits from a base class, then the entire type inherits from that class. Each partial part my inherit from a different interface, but the final type must implement all the interfaces listed by the partial class definitions. Any class, struct, or interface members declared in a partial definition are available to all the other parts. Nested types can be partial, even if the containing type is not partial
public class MyClass { partial class MyPartialClass { ... } ... partial class MyPartialClass { ... } } •
At compile-time, attributes of partial-type definitions are merged together.
[Synchronized] public partial class MyClass { ... } [Serializable] public partial class MyClass { ... } // Above two code fragments are equivalent to [Synchronized]
[Serializable] public partial class MyClass { ... } •
The following are merged together for all partial class definitions: XML comments, interfaces, generic-type parameter attributes, class attributes, and members. For example:
public partial class Car : IEngine, IBody { ... } public partial class Car : Vehicle { ... } // Above two code fragments are equivalent to public partial class Car : Vehicle, IEnging, IBody { ... }
READONLY Applies To: fields readonly is a modifier that can only be applied to class fields. A readonly field cannot be assigned a value during program execution except as part of its declaration or in a constructor of the same class. See readonly in Classes section for a full example.
SEALED Applies To: class methods, indexers, properties, and events A sealed class is one that cannot be inherited. It is typically used to prevent accidental inheritance of the class. struct are implicitly sealed and thus cannot be inherited. See Sealed Classes in Classes section.
STATIC Applies To: classes (new in C# 2.0), fields, methods, properties, events, operators, and constructors. A static type or class member belongs to the class itself rather than to a specific object. Therefore, there is exactly only one copy of each static field in a class. Note the following: • • •
A constant is implicitly a static member. A static member cannot be referenced through an instance. You cannot use the this keyword with a static class member.
UNSAFE Applies To: Any callable class member (methods, indexer, property, constructor and so on - but not for static constructors) The unsafe keyword is required for any block of code that uses pointers. Note that the scope of the unsafe keyword extends from the parameter list to the end of the class member. For example: unsafe public void Square( int *pN ) { *pN = (*pN) * (*pN); }
To compile .NET code that contains an unsafe code you must use the /unsafe compiler option. See Unsafe chapter for more details.
VIRTUAL Applies To: methods, properties, events, and indexers. The virtual keyword is used to indicate that the implementation of the affected class member depends on the runtime-type of the object being invoked. So when a virtual method is invoked, the runtime-type of the invoked object is checked for an overriding member. The overriding member in the most-derived class is then called. Note the following points: • •
By default, class members are non-virtual. You cannot override a non-virtual method. The virtual modifier cannot be used on declarations with static, abstract or override.
See Virtual Methods in Classes section
VOLATILE Applies To: fields. The volatile keyword indicates that a filed can be modified in the program by the operating system, hardware, or another concurrently executing thread. A volatile field is usually used for fields that will be accessed by multiple threads without using the lock statement to synchronize access. Using the volatile modifier ensures that one thread retrieves the most-up-to-date value written by another field. See Volatile fields in Classes section.
CLASSES
SUMMARY • • • • • • • • • • •
Class Declarations Class Members Constants Fields Methods Properties Events Indexers Operators Constructors Destructors
CLASS DECLARATIONS A class declaration is a type-declaration and takes the following form: [attributes] [modifiers] class identifier [:base-list] {
/* Class body */ } [;] For example: [Synchronized] public class MyClass : MyBaseClass, Interface1, Interface2, Interface3 { /* Class body */ }; [modifiers] can be either new, public, protected, internal, private, sealed, static (new in C# 2.0) or abstract. The new modifier is only permitted on nested classes and it specifies that the class hides an inherited member by the same name. It is a compile-time error for the new keyword to appear in a class declaration. The remaining modifiers control the accessibility of the class. Depending on the context in which the class was defined, some of these remaining access modifiers may not be permitted. For example, a class declared within a namespace must be wither public or internal. Finally, The [base-list] is usually zero or one base class and zero or more interfaces.
Abstract Classes The abstract modifier is used to indicate that the class is incomplete and that it is intended to be used as a base-class only. Note some of the characteristics of abstract classes: • • • •
An abstract class is one that cannot be instantiated directly via new. An abstract class can contain member methods that can be either abstract or nonabstract. When a non-abstract class inherits from an abstract base class, the non-abstract class must implement all of the inherited abstract members of the abstract base class by overriding them. An abstract class cannot be sealed.
public abstract class AbstractClass1 { public abstract void foo(); public void bar() { Trace.WriteLine("AbstractClass1.bar{}"); } } public class ConcreteClass1 : AbstractClass1 { // Must provide implementation of all abstract members of an abstract class. public override void foo() { Trace.WriteLine("ConcreteClass1.foo()"); } } private void button1_Click(object sender, System.EventArgs e) { // Instantiating abstract classes ConcreteClass1 ob1 = new ConcreteClass1(); ob1.foo(); // ConcreteClass1.foo() ob1.bar(); // AbstractClass.bar() } An abstract class is closely related to an interface in the sense that both declare a 'contract' that must be adhered to by clients. See interface for more info.
Sealed Classes A sealed class is that a class that cannot be used as a base class. In other words, it cannot be derived from; it is at the end of the derivation chain. The sealed keyword is mostly used to prevent unintended derivation, but it also enables certain runtime optimizations; for example, because a sealed class is known to never have any derived classes, it is possible to transform virtual function member invocations on sealed classes into non-virtual invocations (Recall that virtual member method invocation involves looking up and invoking an entry from a vtable - which incurs an extra level of indirection.)
Base Classes Note the following points about base classes: •
If a class declaration has no base class, or if the class only derives from interfaces, then the direct base class is assumed to be object.
// The direct base class is assumed to be object public class A { ... } // The direct base class is A public class B : A { ... } • • •
The set of base classes of a class type is the transitive closure of the direct base class relationship. In the above example, the base classes of class type B is A and object. The direct base class must be at least as accessible as the class type itself: The direct base class of a class type must not be any of the following: System.Array, Sysem.Delegate, System.Enum, or System.ValueType. Every class has exactly one direct base class. Except the object class which has no direct base class and is the ultimate base class of all other classes. A class directly depends on its direct base class, and directly depends on a class within which it is immediately nested (if any). Therefore, the complete set of classes upon which a class depends is the transitive closure of the "directly depends on" relationships.
• •
Static Classes (C# 2.0) A class can be declared static indicating that it only contains static member. It is not possible to create instances of static classes with the new keyword. Static classes are loaded automatically by the .NET Framework when the program or the namespace containing the class is loaded. In fact a static class is similar to a normal class with static members and private constructors. Static classes should be used to associate to contain methods that are not associated with a particular object. Utility classes are often static as they do not require an object instance to perform their work. The main features of static classes are: 1. 2. 3. 4.
They only contain static members. They cannot be instantiated. They are sealed and cannot be inherited. They cannot contain instance constructors (although they can contains static constructors to assign values or set up some static state).
For example, a class to display computer info does not really need to be attached to a specific instance of the class. You can declare such a class static as follows: static class ComputerInfo { // Data members static private string strComputerName;
private int nCPUCount; // error CS0708: 'NewFeatures.ComputerInfo.nCPUCount': cannot declare instance members in a static class // Constructors static ComputerInfo() { /* Initialize static members to a known state */ } public ComputerInfo() { } // error CS0710: Static classes cannot have instance constructors // Methods static public string GetComputerName() { /* Your implementation */ } static public int GetTickCount() { /* Your implementation */ } static public string[] GetDrives() { /* Your implementation */ } public int GetCPUCount() // error CS0708: 'GetCPUCount': cannot declare instance members in a static class { /* Your implementation */ } }
CLASS MEMBERS C# classes can contain the following categories of members: • • • • • • • •
• •
Constants Represent constant values within the class. Fields Represent variables of the class. Methods Represent computations and actions performed by the class. Properties Represent named characteristics used to read/write those characteristics. Events Represent notifications that can be generated by the class. Indexers Permit instances of classes to be indexed in the same way as arrays. Operators Define which expression operators can be applied to instances of classes. Constructors Instance constructors implement actions required to initialize instances of the class, where static constructors implement actions required to initialize the class itself (typically used to initialize static members) Destructors Implement clean-up actions before an instance is permanently discarded from memory. Types Represent types that are local to a class.
public class Vehicle : ICloneable { // Fields private int nMoel; private string strManufacturer // Constants const int WEIGHT = 1000; // Constructors
// field // field
public Vehicle() { ... } public Vehicle( int nModel ) { ... } // Methods void Start() { ... } // Properties int Mileage { get { ... } set { ... } } // ... }
Inheritance Inheritance means that a class implicitly contains all members of its direct base class, except for instance constructors, destructors, and static constructors of the base class. A class is permitted to declare a member with the same name or signature as an inherited member. When this occurs, the derived class member is said to hide the base class member. Derived class can hide inherited members by declaring new members with the same name or signature. Note that hiding a member does not remove it - hiding only makes the hidden member inaccessible form the derived class. Because only public inheritance is allowed in C#, the is-a relation ship always holds true. Therefore, if class Student derived from class Person, then public inheritance means that Student is-a Person, and hence any code that expects an instance of Student can be passed an instance of Person, because Student is-a Person. This also implies that an instance of Student can always be treated as instance of Person. Virtual methods, properties and indexers enable polymorphic behavior wherein actions performed by the overridden versions of these virtual members will vary depending on the run-time type of the instance through which the member function is invoked. Note that unlike C++, C# classes, • • •
Only support public inheritance. Can inherit from one base class only. However, a C# class is free to inherit from more than one interface. Can only be created using the new operator.
Consider the following examples: /* No inheritance */ public class MyClass { } /* Single inheritance */ public class MyClass : SomeBaseClass { } /* No inheritance, but implements two interfaces */ public class MyClass : IEnumerable, ICloneable { } /* Single inheritance, and implements two interfaces */ public class MyClass : SomeBaseClass, IEnumerable, ICloneable { }
Access Modifiers Class members may have any of five possible kinds of declared accessibility:
• • • • •
public protected private (default if none was provided) internal protected internal
Static and Instance Members Class members (fields, methods, indexers, etc.) can be either static members or instance members. It is useful to think of static members as belonging to the class itself, and instance members as belonging to objects (instances of classes). Static members are declared using the static keyword. Static members have the following characteristics: • •
A static field identifies only one storage location. No matter how many instances of a class are created, there will always be one copy. A static function member does not operate on a specific instance. Therefore, it is a compile-time error to refer to this in such a function.
Nested Types A nested type is a type declared within a class or a struct (a non-nested type is one that is declared within a compilation unit or namespace.) // class A is a non-nested class public class A { ... // class B is a nested class. Default accessibility is private class B { ... } } A nested type declared with a class can have any of the five forms of declared accessibility: public, protected internal, protected, internal, private, and like other class members defaults to private. Recall that non-nested types can have public or internal declared accessibility only. However, a nested type declared within a struct can have only public, internal, or private declared accessibility, and like other struct members defaults to private. A nested type may also use the new modifier to hide a base member of the containing class: public class Base { public void X() { Trace.WriteLine("Base.foo()"); } } public class Derived : Base { new public class X { public void F() { Trace.WriteLine("MyNested.foo()"); } } } Derived d = new Derived(); Derived.X dx = new Derived.X(); dx.F();
Also note that a nested type and its containing type do not have a special relationship with respect to this. Specifically, this within a nested type cannot be used to refer to an instance of the containing type. If the nested type needs to access instance members of its containing type, it can do so through by having a constructor or other property that takes the this variable of the containing class.
CONSTANTS A constant is a class member that represents a constant value; i.e., a value that can be computed at compile-time. A constant declaration takes the following form: [attributes] [modifiers] type identifier = constant-expression For example: public class A { public const double PI = 3.14157, ZERO = 0.0; public const double ONE = ZERO + 1; ... } Note that a constant with a class is semantically equivalent to a static member in the sense that each class instance will get the same value for that constant. However, a constant neither requires nor allows the static keyword to be supplied. A constant-expression is an expression that can be fully computed at compile-time. Note that because the new operator is not allowed in a constant-expression, and because the only way to create a non-null value of a reference type (other than string) is to use new, then the only possible values for constants of reference types (other than string) is null. Constants are allowed to depend on other constants within the same program as long as the dependencies are not of a circular nature: public class A { public const int X = 1; } public class B { public const int Y = A.X + 1; }
FIELDS A field is a class member that represents a variable associated with an object of a class. Declaring a field takes the following form: [attributes] [modifiers] type identifier = variable-initializer Where [modifiers] can be: • • • •
new public protected internal
• • • •
private (default) static readonly volatile
Note that initializing a field is optional: •
•
If a field is not initialized, the initial value of a field whether it is a static field or an instance field is the default value of the field's type. This initialization happens when the program is run by having the memory manager initialize field memory to all-bits-zero before it is allocated for use. If a field was initialized, then: • for static fields this initialization correspond to an assignment statement that is executed at an implementation-dependent time prior to the static constructor of the class (if any) and prior to the first use of a static field of that class. • for instance fields this initialization correspond to an assignment statement that is executed during class instantiation using any of the instance constructors.
public class A { private int defaults to 0 private bool defaults to false private double private static
m_nIndex;
// m_nIndex
m_bStatus;
// m_bStatus
m_dX = 1.0, m_dY = 2.0; int m_nX = -1, m_nY = -1;
private static bool m_bStaticBool; m_bStaticBool defaults to false private static bool m_bInstanceBool = true; m_bInstanceBool defaults to false
// //
private static int x = y // Possible to initialize a static field with another static field that has not yet been initialized private static int y; // Result of x = y is undefined until you explicitly assign a value }
Static and instance fields A static field is not part of a specific instance; instead it identifies exactly one storage location no matter how many instances of the containing class are created. An instance field (not qualified with static) on the other hand belongs to an instance; there is a separate memory location in each class instance. See Static and Instance Members for more details.
Readonly fields Fields declared with readonly keyword are ... read only! Direct assignment to readonly fields can only occur as follows: • •
Non-static fields These fields can be initialized during declaration or in an instance constructor. Static-fields These fields can be initialized during declaration or in an static constructor.
Note that using an instance constructor (for read-only instance fields) or a static constructor (for read-only static fields) allows you to pass the read-only field as an out or ref parameter.: public class C {
// readonly fields initialized during declaration private readonly int N = 10; private static readonly int M = -10; // readonly fields initialized in constructors private readonly int O; private static readonly int P; public C( int o ) { O = o; } static C() { P = 10; } } readonly instance fields are specific to each instance. However, static readonly fields are not specific to any instance as their value will be shared by all instances. static readonly fields are semantically similar to constants, so when do you use which? • •
A static readonly field is useful when a symbolic name for a constant value is required, but when the type of the value is not permitted in a const declaration. A static readonly field is useful when the value cannot be computed at compiletime.
In the example below, Brush_10, Black, and White cannot be declared as constants because their values cannot be computed at compile-time: public class MyColor { /* Data members */ // The appropriate way to define 'constants' for a class type private MyBrush( 10 private 0, 0 ); private 255, 255 );
static readonly MyBrush Brush_10 = new ); // OK static readonly MyColor Black = new MyColor( 0, // OK static readonly MyColor White = new MyColor( 255, // OK
// A 'const' declaration does not allow the new keyword or a class type to be used private const MyBrush Brush_1 = new MyBrush( 1 ); // The expression being assigned to 'MyColor.Brush_1' must be constant private const MyColor Red = new MyColor( 255, 0, 0 ); // The expression being assigned to 'MyColor.Red' must be constant private const MyColor Green = null; // OK // byte red, green, blue; // Constructors public MyColor( byte r, byte g, byte b ) { red = r; green = g; blue = b; } }
Volatile fields A volatile field is one that be modified in the program by something such as the operating system, hardware, or a concurrently executing thread. Access to non-volatile fields in multithreaded programs can lead to unexpected results due to optimization techniques performed by the compiler. For volatile fields such optimizations are restricted: • •
A read of a volatile field is called a volatile read - it is guaranteed to occur prior to any references to memory that occur after it in the instruction sequence. A write of a volatile field is called a volatile write - it is guaranteed to occur after any memory references prior to the write instruction in the instruction sequence.
These instructions ensure that all threads will observe volatile writes performed by any other thread in the order they were performed.
METHODS A class method is a class member that represents a computation action that can be performed by an object or class. Declaring a class member takes the following form: [attributes] [modifiers] return-type identifier (formalparameter-list) Where [modifiers] can be: • • • • • • • • • • •
new public protected internal private (default) static virtual sealed override abstract extern
For abstract and extern modifiers, the method body should consist of a semi-colon. For all others, the method body consist of a block which specifies the statements to be executed. The signature of a method consists of the method's name and type and kind (value, reference, output) of each of its format parameters. Note that the signature of a method does not specifically include the return type not does it include the params modifier that may be specified for the rightmost parameters.
Method Parameters The format-parameters-list takes the form of one of the following: • • •
fixed parameters fixed parameters, parameter-array parameter-array
where fixed parameters takes the form of : [attributes] [ref|out] type identifier and parameter array takes the form of:
[attributes] params array-type identifier There are four kinds of formal parameters: • • • •
Value Parameters. Reference Parameters. Out Parameters. Parameter Arrays.
Value Parameters A parameter declared with no modifiers is a value parameter. A value parameter corresponds to a variable local to the function that gets its initial value for the corresponding argument supplied in the method invocation. Any changes to value parameters affect the local storage area only and have no effect on the actual argument given to the method invocation. In other words, changes to value parameters within the function body are not reflected back to the caller. Reference Parameters A parameter declared with the ref keyword is a reference parameter. Unlike a value parameters, a reference parameter does not create a new local storage location. Instead, a reference parameter has the same storage location as the variable given as the argument in the method invocation. This means that any changes to the parameter in the method will be reflected back in the caller. Variables representing reference parameters must always be assigned before using them in a method invocation. The method invocation must also supply the ref keyword for any variable representing a reference parameter. Also note that an overload will result if two methods differ only in their use of ref. class MethodParameterTest { public void Swap( ref int x, ref int y) { int nTemp = x; x = y; y = nTemp; } } MethodParameterTest obMPT = new MethodParameterTest(); int x = 10; int y = 20; obMPT.Swap( ref x, ref y ); // x = 20, y = 10 Out Parameters A parameter declared with the out keyword is output parameter. Similar to a reference parameters, an output parameter has the same storage location as the variable given as the argument in the method invocation. This means that any changes to the parameter in the method will be reflected back in the caller. out parameters are particularly useful when you want a method to have multiple return values, as any given method can have zero, one, or more out parameters. Variables representing out parameters do not need to be assigned before using them in a method invocation. The method invocation must also supply the out keyword for any variable representing an output parameter. Within the method, an output parameter is considered unassigned and must be assigned before its value can be used. Also note that an overload will result if two methods differ only in their use of out.
class MethodParameterTest { public void GetTwoRandomNumbers( out int x, out int y) { x = 100; // out parameters must be assigned a value before function returns y = 200; // out parameters must be assigned a value before function returns } } int x; int y; obMPT.GetTwoRandomNumbers( out x, out y ); 200
// x = 100; y =
Parameter Arrays A parameter declared with the params keyword is a parameter array. Parameter arrays are used to pass a variable number of parameters, similar to the ... syntax used in a C++ function. If a format parameter list includes a parameter array, it must the right-most parameter and it must be a single-dimensional array. You cannot combine the out or ref modifiers with the params modifier. The following example illustrates how a declare a method with a params parameter and how to call it: class MethodParameterTest { public void PassParamterArray( float fNumber, params string[] aParams ) { foreach(string s in aParams) { Trace.WriteLine( s ); } } } MethodParameterTest obMPT = new MethodParameterTest(); // Method 1: pass an array. Here the argument for the array is a single expression of a type that can be // implicitly converted to the parameter array type string[] aStrings = {"One", "Two", "Three" }; obMPT.PassParamterArray( 12.34F, aStrings ); // Method 2:Pass a variable number of arguments. Here the compiler will create an array whose type and length // corresponding to the given arguments., initializes the array with the given argument values and uses the newly // created array instance as the actual argument. obMPT.PassParamterArray( 56.78F, "One", "Two", "Three", "Four" ); // array length will be 4 obMPT.PassParamterArray( 90.00F ) ; // array length will be 0
Passing Method Parameters Recall the following: • •
.NET types can be either value-types or reference-types (ignore the pointer types in unsafe code as they are irrelevant to this discussion) .NET types can either be passed by-value or by-reference.
Therefore, there are four cases for passing method parameters in C#':
• • • •
Passing a value type by- value. Passing a value type by- reference. Use the ref or out keywords. Passing a reference type by- value. Passing a reference type by- reference. Use the ref or out keywords.
Also recall the following: • •
Passing any type by-value to a function member means that the the function member will be working with a copy, and hence changes to the variable will not be seen by the caller. Passing any type by-reference to a function member means that the the function member will be working on the same location as the passed variable, and hence changes to the variable will be seen by the caller.
Now note the following: • •
•
The default for passing value-types is by-value. This means that a copy of the actual data will be passed and changes to the copy will not be seen by the caller. The default for passing reference-types is also by-value. This means that a copy of the reference will be passed. This further means that changes to the underlying data pointed to by the reference will be seen by the caller, however, changes to the reference itself, i.e., re-assigning the parameter to a different memory location works only inside the method and does not affect the original variable. Passing a reference type by-reference means that the actual reference itself will be passed. This implies two things: Changes to the underlying data will be seen by the caller, and changes to the reference (i.e., re-allocating the variable) will also be seen by the caller.
The following table summarizes the points above:: Case
Keyword
Passing a none value type by- value
Passing a out or value type ref byreference
Passing a none reference type byvalue
Note •
A new local storage location is created in the method, hence a copy of the data is used.
•
Changes to this value in the method are not seen by the caller.
•
The function uses the same storage location as used by the original caller.
•
All changes to the variable will be seen by the caller.
•
A new local storage location is created in the method, hence a copy of the reference is used. Changes in the method to the data pointed-to by the reference variable are seen by the caller.
•
Passing a out or reference ref
•
Changes to the reference itself (i.e., re-assigning or changing the memory location) are not reflected back to the caller.
•
The function uses the same storage location as used by
type byreference
the original caller. •
All changes to the variable will be seen by the caller. This means that the underlying data as well as the memory location itself can be reassigned/changed.
Examples: public class PassingParemters { // Changes not seen by caller public void PassValueTypeByValue( int n ) { n =10; } // Changes seen by caller public void PassValueTypeByReference( ref int n ) { n = 10; } // Changes not seen by caller public void PassReferenceTypeByValue( string str ) { str = "World"; } // Changes seen by caller public void PassReferenceTypeByReference( ref string str ) { str = "World"; } // Changes not seen by caller public void PassReferenceTypeByValue( ArrayList al ) { al = new ArrayList(); al.Add( "hello"); al.Add( "world" ); } // Changes seen by caller public void PassReferenceTypeByReference( out ArrayList al ) { al = new ArrayList(); al.Add( "hello"); al.Add( "world" ); } } // Passing parameters test PassingParemters obPP = new PassingParemters(); // Passing a value-type by-value int n = 0; obPP.PassValueTypeByValue( n );
// n = 0
// Passing a value-type by-reference int m = 0; obPP.PassValueTypeByReference( ref m );
// m = 10
// Passing a reference-type by-value string str1 = "Hello"; obPP.PassReferenceTypeByValue( str1 ); "Hello" // Passing a reference-type by-reference string str2 = "Hello"; obPP.PassReferenceTypeByReference( ref str2 ); "World" // Passing a reference-type by-value ArrayList al1 = null; obPP.PassReferenceTypeByValue( al1 ); null because it is passed by value // Passing a reference-type by-reference ArrayList al2; obPP.PassReferenceTypeByReference( out al2 ); properly allocated with a count of 2 items
// str1 =
// str2 =
// al1 remains
// al2 is
Static and Instance Methods When a method declaration includes the static keyword, the method is said to be a static method. When no static modifier is present, the method is said to be an instance method. Note that a static method does not operate on an instance and hence it is a compile-time error to use the this keyword inside a static function. However, an instance method operates on a specific instance and accessing the this keyword is allowed.
Virtual Methods A virtual method is a method declared with the virtual keyword. The implementation of a non-virtual method (a method without the virtual keyword) is invariant: the implementation will be the same whether the method is invoked on an instance of the class on which it is declared, or on an instance of a derived class. In contrast, the implementation of a virtual method can be superseded in derived classes. The process of superseding the implementation of an inherited virtual method is known as overriding. Note the following important differences between virtual and non-virtual method invocation: • •
In a virtual method invocation, it is the run-time type of the instance that determines which method to invoke. In a non-virtual method invocation, it is the compile-time type of the instance that determines which method to invoke.
To help differentiate between compile-time vs. run-time type consider the following: public class A { void A1() { ... } virtual void A2() { ... } } public class B : A { override void A2() { ... } } A obA = new A();
// obA has a compile-time type of A and
a run-time type of A B obB = new A(); a run-time type of A
// obB has a compile-time type of B and
obA.A1(); obA.A2();
// Invokes A.A1() // Invokes A.A2()
obB.A1(); obB.A2();
// Invokes A.A1() // Invokes B.A2()
When a method declaration contains the override keyword, the method is said to be overridden. Use the override keyword to override (supercede or specialize) the implementation of an inherited virtual method. Note that while a virtual method declaration introduces a new method, an override method declaration specializes an existing inherited virtual method by providing a new implementation. An override declaration can access the overridden base method using the base keyword: void override foo() { // Call the overridden base method. base.foo(); ... } Note that only by introducing the override keyword can a method override another method. In all other cases, a method with the same signature as an inherited method simply hides the inherited method. In the example below, Derived1.f() hides Base1.f(): public class Base1 { public virtual void f() { Trace.WriteLine("Base1.f()"); } } public class Derived1 : Base1 { // Hides Base1.f() because 'override' was not specified public virtual void f() { Trace.WriteLine("Derived1.f()"); } } public class Derived2 : Derived1 { // Overrides Derived1.f() public override void f() { Trace.WriteLine("Derived2.f()"); // Call base method base.f(); } } // Calls Base1.f() because Derived1 did not override Base1.f() Base1 ob1 = new Derived1(); ob1.f(); // Calls Base1.f() because Derived2 did not override Base1.f(). Derived2 overrode Derived1.f()
Base1 ob2 = new Derived2(); ob2.f(); // Calls Derived2.f() because Derived2 overrides Derived1().f() Derived1 ob3 = new Derived2(); ob3.f(); // Calls Derived2.f() because Derived2 overrides Derived1().f() Derived2 ob4 = new Derived2(); ob4.f(); Note also that the sealed keyword can be used when overriding an inherited virtual method to prevent a derived class from further overriding the method. Note the following code which extends the pervious example by marking Derived2.f() as a sealed override: public class Derived2 : Derived1 { public sealed override void f() { Trace.WriteLine("Derived2.f()"); // Call base method base.f(); } } public class Derived3 : Derived2 { // error CS0239: 'Primer.Derived3.f()' : cannot override inherited member 'Primer.Derived2.f()' because it is sealed public override void f() { Trace.WriteLine("Derived3.f()"); // Call base method base.f(); } } Derived3 attempts to further specialize f() which was inherited from Derived2. However, Derived2 declared f() as a sealed override, and hence Derived3 attempts to override a sealed method.
Abstract Methods An abstract method is a method declared with the abstract modifier. An abstract method is an implicitly virtual method except that it cannot have the virtual modifier. An abstract method declaration differs from a virtual method declaration in the following areas: • •
An abstract method declaration declaration introduces a new virtual method, but unlike a virtual method declaration, an abstract method declaration does not provide an implementation. Non-abstract derived classes are required to provide their own implementation by overriding the inherited abstract method. Whereas a a virtual method declaration does not require its derived classes to provide their own implementation since the derived classes inherit the default implementation of the base class virtual method.
Therefore, an abstract method declaration introduces a new virtual function and forces its derived classes to provide their own implementation. To use abstract methods, they must be declared in abstract classes and overridden in derived classes:
public abstract class Shape { public abstract void Paint(); // Abstract methods have no body and must be contained in abstract classes } public class Rectangle : Shape { public override void Paint() { Trace.WriteLine( "Rectangle.Paint()" ); } } Shape obShape = new Rectangle(); obShape.Paint();
// Calls Rectangle.Paint()
The abstract keyword can also be used on a virtual method to force the re-implementation of the method in derived classes. This can be though as a way to branch the virtual method into a new implementation. In the following example, class declares A virtual method, class B overrides this method with an abstract method, and class C then overrides the abstract method to provide its own implementation. However, class D overrides the method in class A. This results in two branches:
public class A { public virtual void f() { Trace.WriteLine("A.f()"); } } abstract public class B : A { public abstract override void f(); Branches implementation of f() }
//
public class C : B { public override void f() { Trace.WriteLine("C.f()"); } overrides the abstract B.f() }
//
public class D : A { public override void f() { Trace.WriteLine("D.f()"); } overrides the virtual A.f() }
//
A obC = new C(); obC.f();
// Calls C.f()
A obD = new D(); obD.f();
// Calls D.f()
External Methods
An external method in C# is a method declared with the extern modifier and implemented externally perhaps in another language like C++ or VB.NET. Therefore, an external method declared in C# has no implementation body. The extern modifier is typically used with the DllImport attribute allowing external methods to be implemented in DLLs (note that when a method declaration includes a DllImport attribute, the method declaration must also include a static modifier): [DllImport("kernel32", setLastError=true)] static extern bool SetCurrentDirectory( string strCurrent ); // No implementation body (just like abstract methods)
PROPERTIES A property is a class member that provides access to a characteristic of an object or class. Examples of properties include size of a window, ID of a student, length of a string and so on. Properties are a natural extension of fields in that they provide a simple way for getting/setting property values, and at the same time, provide for a mechanism that associates actions/computations when getting/setting values. Declaring a property takes the following form: [attributes] [modifiers] type identifier { accessor-declarations } Where [modifiers] can be: • • • • • • • • • • •
new public protected internal private (default) static vritual sealed override abstract extern
Even though the syntax for accessing a property is the same as accessing a variable, a property does not denote a variable. Therefore, it has no memory location and parameter modifiers out and ref cannot be used to pass properties as function arguments. Similar to fields and methods, a static property includes the static modifier and is not associated with a specific instance, while an instance property does not include the static modifier and is associated with a specific object instance.
Accessors The accessor-declarations consist of a get-accessor-declaration only for read-only access, or a set-accessor declaration only for write-only access, or both for read and write access. The following example shows a very basic and simple property with both a get-accessor-declaration and set-accessor declaration: public class Student { // Fields private string m_strName;
... // Properties string Name { get { return m_strName; } set { m_strName = value; } parameter of the set accessor } }
// 'value' is the implicit
Note that for extern and abstract properties, each of its access-declarations should consist of a semi-colon with no body // Abstract and external property have not get / set body abstract int Color { get; set; } extern int Size { get; set; } When a derived class declares a property by the same name as an inherited property, the derived property hides the inherited property with respect to both reading and writing: public class Base { int ID { get { ... } } } public class Derived : Base { new int ID { set { ... } } } Derived obD = new Derived(); int nID = obD.ID; // Compile-time error because property ID is write-only It is considered as a bad programming practice for get accessors to have observable side effects. This is because invoking a get accessor is conceptually equivalent to reading a value: public class Shape { private int m_nX; public int X { return m_nX++; not increment } }
// Bad programming style. Should
This "no-side effect" convention does not mean that the get accessor should always be written to return values stored in fields. In fact, get accessors often compute the value of a property by invoking multiple methods and accessing multiple fields. For example, a TransactionCount property on a data access object can be used to query the database for number of currently active transactions!
Properties are often used to delay initialization of a field until the moment it is first used: public class Console { private static TextReader reader; public static TextReader In { if (reader == null) reader = new StreamReader( ... ); return reader; } } // Main application string strLine = Console.In.ReadLine(); if not already created by a previous call
// TextReader created
Finally, note that exposing fields through properties is not any less efficient than exposing fields directly. In fact, when a property is non-virtual and contains a small amount of code, the execution environment may replace calls to accessors with the actual code of the accessors. This is known as in-lining and makes property access as efficient as field access.
Asymmetric Accessor Accessibility (C# 2.0) Prior to C# 2.0, by default the get and set accessors have the same visibility or access level. In other words, a set property cannot be protected for example while the get property is public. However, sometimes it is quite useful if access is restricted to only of them, typically restricting access to the set accessor while keeping the get accessor public. For example: // Asymmetric property public string Name { get // The get accessor gets the accessibility level of the property itself (public) { return strName; } protected set // The set accessor is explicitly restricted by another access modifier { strName = value; } } Note the following restrictions on access modifiers on accessors: 1. As known from interfaces, you cannot apply access modifiers on interface declaration or an explicit interface member implementation. 2. You can use asymmetric accessors only if the property or indexer has both set and get accessors. In this case, the modifier is allowed on only one of the two accessors. 3. If the property or indexer is an override, the accessor modifier must match the accessor of the overridden accessor, if any. 4. The accessibility level on the accessor must be more restrictive than the accessibility level on the property or indexer itself. The following code illustrates some of the points above:
class Accessors { private string strName; private int nID; protected int nQuantity; // Assymetic property public string Name { // The get accessor gets the accessibility level of the property itself (public). // If you give the get access an accessibility level, you get error CS0274: Cannot // specify accessibility modifiers for both accessors of the property or indexer // 'NewFeatures.Accessors.Name' get { return strName; } // The set accessor is explicitly restricted by another access modifier protected set { strName = value; } } public int ID { get { return nID; } // If accessibility level is made public, you get error CS0273: The accessibility // modifier of the 'NewFeatures.Accessors.ID.set' accessor must be more restrictive // than the property or indexer 'NewFeatures.Accessors.ID' private set { nID = value; } } // Read notes for the overridden Quantity property public virtual int Quantity { // Because the set accessor is assigned an accessibility level, no accessibility // level can be specified for the get accessor get { return nQuantity; } // Notice the accessor accessibility level protected set { nQuantity = value; } } } class DerivedAccessor : Accessors
{ public override int Quantity { // Still cannot use an access modifier here get { return nQuantity; } // Must use the same accessibility level as in the virtual property protected set { nQuantity = value; } } } Example for implementing an interface with with asymmetric access modifiers: interface IMyInterface { // As per interface declaration rules, no access modifier can be applied to the Name property. // Also no access modifier can be applied to the get accessor. string Name { get; } } public class MyClass : IMyInterface { private string strName; // Interface implementation. Name property must be public because it is an interface member public string Name { // Cannot use an access modifier here because this is an interface implementation get { return strName; } // Accaccess modifier is allowed here because interface property Name did not specify // a set accessor protected set { strName = value; } } } Another example which illustrates how a property in a derived class can be hidden when using a more restrictive access modifier: // A class with two normal public properties public class BaseProperties { // Member variables private string name = "BaseProperties_name"; private int id = -1; // Properties
public string Name { get { return name; } set { name = value;} } public int ID { get { return id; } set { id = value;} } } // Specialzes BaseProperites by modifying the inherited properties public class DerivedProperties : BaseProperties { // Member variables private string name = "DerivedProperties_name"; private int id = 100; // Properties // Hides the inherited Name property public new string Name { get { return name; } set { name = value; } } // Hides the inherited ID property and makes it inaccessible in the main program // In Pre .Net 2.0, supplying private on the property ID would have given // a compile time error new private int ID { get { return id; } set { id = value; } } } private static void TestAsymmetricProperties() { BaseProperties obB = new BaseProperties(); obB.Name = "X"; // Accesses base class instance obB.ID = 1; // Accesses base class instance DerivedProperties obD = new DerivedProperties(); obD.Name = "Y"; // Accesses derived class instance obD.ID = 10; // Accesses base class instance } Output:
Virtual, abstract, override and sealed properties Note the following: •
A virtual property is one that is declared with the virtual keyword. The virtual keyword specifies that the current implementations of the accessors are virtual and that derived classes are free to specialize them using the override keyword.
•
• •
•
An abstract property on the other hand is one declared with the abstract keyword but does not provide an actual implementation of the accessors. Because abstract property declarations are only permitted on abstract classes, it is therefore, a requirement for nonabstract derived classes to provide their own implementation. This logic is the same as it is for virtual and abstract methods. A property declaration that includes both abstract and override modifiers specifies that the property is abstract and overrides a base virtual property. This results in a branch situation which was discussed in the methods section. When overriding properties, if the inherited property had a single accessor (i.e., read-only or write-only), the overriding property must only override that accessor. If the inherited property included both accessors, the overriding property can override either a single accessor or both of them. An overriding property may include the sealed keyword to indicate that this property may not be further specialized by derived classes.
For example, public abstract class A { // Data members int m_nY, m_nZ; // Properties public abstract int X { get; } public virtual int Y { set { m_nY = value; } } public virtual int Z { get { return m_nZ; } set { m_nZ = value; } } } public class B : A { // Overridden properties public override int X { get { /* provide another implementation */ } set { ... } // error CS0546: 'B.X.set': cannot override because 'A.X' does not have an overridable set accessor } public override int Y { get { ... } // error CS0545: 'B.Y.get': cannot override because 'A.Y' does not have an overridable get accessor set { /* provide another implementation */ } } public override int Z { get { /* provide another implementation */ } set { /* provide another implementation */ } } }
EVENTS An event is a class member that allows an object or class to provide notifications. Declaring an event takes the following form: [attributes] [modifiers] event type variable-declarators; [attributes] [modifiers] event type member-name { eventaccessor-declarations } Where [modifiers] can be: • • • • • • • • • • •
new public protected internal private (default) static vritual sealed override abstract extern
type is the delegate to which you want to associate this event and access-declarators are used to add and remove event handlers in client code. When working with events, you typically work with three components - an event, a delegate and an event handler. An event is fired by a class in certain cases by simply calling the event. A delegate represents the signature of a callable entity (i.e., a function) that will be called by the event. An event handle is a client-side function whose signature must match that of the delegate and that will be called when the event fires. The event keyword allows you to specify a delegate that will be called upon the occurrence of some event in your code. The delegate can have one or more methods associated with it that will be called when the event for that delegate fires. To create C# events:
1. Create or identify a delegate If you are defining your own event, ensure that there is a delegate to use with the event.
2. Create a class that defines the event
3. 4.
This class contains: • An event created from the delegate. • An optional method that verifies that an instance of the delegate declared with the event keyword exists. • Methods that fire (call) the event. Define one or more classes that connect methods (event handlers) to the event Each of these classes will include the definition of the methods that will be associated with the event Use the event Use the event by creating objects of the class that contains the event declaration and firing the event in specific cases
For example, // Step 1: Create a delegate to represent the function(s) that will be called by the event. // Event handlers must follow this signature public delegate void ProcessFault(string str);
// Step 2: Create a class to declare the event and call it public class MyMachine { // Declare an event that when fired will call functions whose signature match that of 'ProcessFault' public event ProcessFault Break; public void Start() { // Code to start the machine ... // If some parameter is below threshold, fire the Break event. Because the 'Break' event is associated // with a delegate whose signature takes a string as a parameter, the event must supply that string, // which in turn will be passed to the event handler(s). The following line will therefore (internally) // call all methods (i.e., event handlers whose signature must match that of the delegate) associated // with the event Break( "machine stopping" ); } } // Create a simple machine object MyMachine ob = new MyMachine(); // Wire up the machine's events. This is how we tell the Break event which functions it should // call when the event is fired. Note that PreFaultHandler and PostFaultHandler are helper functions // whose signature must match that of event's delegate. Note that the compiler provides the // underlying implementation for the += and -= operators. ob.Break += new ProcessFault( PreFaultHandler ); ob.Break += new ProcessFault( PostFaultHandler ); // Start the machine. It may fire events ob.Start(); // These are the event handlers private void PreFaultHandler( string strMessage ) { Trace.WriteLine( "PreFaulHandler - " + strMessage ); } private void PostFaultHandler( string strMessage ) { Trace.WriteLine( "PostFaultHandler- " + strMessage ); } Event declaration typically omit event-accessor-declarations as in the example above. A class that declares events can include event-accessor-declarations when the class wants to have a private mechanism for storing the list of event handlers. The event-accessordeclarations consist of an add and a remove block used to add / remove handlers associated with the event. The add block specifies statements to execute when adding an event handler, while the remove block specifies statements to execute when removing an event handler. In this code segment from the example above: // Wire up the machine's events. This is how we tell the Break event which functions it should // call when the event is fired ob.Break += new ProcessFault( Pre FaultHandler );
The += assignment will call the add code block associated with the Break event to add the event handler to some storage location (perhaps a hash table maintained by the event class), and conversely, the -= assignment will call the remove code block to remove the event handler from the same storage location. In the following example, event-accessor-declarations are added note how the event is fired: public delegate void ProcessFault2(string str); public class MyMachine2 { // Declare storage location for delegates associated with events private ProcessFault2 handler; // This is the event. Note how now it has a body to handle adding/removing delegates public event ProcessFault2 Break { add { handler += value; } remove { handler -= value; } } public void Start() { // Code to start the machine // If some parameter is below threshold, fire an the Break event handler( "machine stopping" ); } } // Test event where add/remove have explicitly specified MyMachine2 ob2 = new MyMachine2(); ob2.Break += new ProcessFault2( FaultHandler ); ob2.Start(); // This is the event handler private void FaultHandler( string strMessage ) { Trace.WriteLine( "FaulHandler - " + strMessage ); } Note that just like methods, properties, and fields, an event can either be a static event (not associated with a specific instance), or it can be an instance event (associated with a specific instance).
INDEXERS An indexer is a class member that allows an object to be indexed in the same way as an array. An indexer is usually added to a class when the class contains an internal collection and you wish to access this internal collection using array-like syntax. Declaring an indexer takes the following form: [attributes] [modifiers] type this [parameter-list] Where [modifiers] can be:
• • • • • • • • • •
new public protected internal private (default) virtual sealed override abstract extern
Note that the static modifier is not permitted. The following shows how to declare and use a simple indexer: public class Grid { // Declare the 2-D array that the indexer of this class will access int[,] aGrid = new int[10,20]; // Declare an indexer to access any element in the 2dimensional array public int this[int nRow, int nColumn] { get { return aGrid[nRow, nColumn]; } set { aGrid[nRow, nColumn] = value; } } } // Create indexers Grid ob = ob[0,0] = ob[0,1] = ob[0,2] =
a grid object and set its grid array using the new Grid(); 1; 10; 100;
// Retireve grid values using the indexer int nVal = ob[0,1]; Note the following points about indexers: • • • •
• •
Unlike other class members, indexers do not have user-defined names. For the parameter-list, at least one parameter must be specified and that ref and out parameters are not permitted/ Even though the syntax for accessing an indexer element is the same as accessing an array variable, an indexer element is not specified as a variable, and therefore, it is not possible to pass an indexer element as a ref or out variable. The signature of an indexer consists of the number and type of its formal elements. The names of the formal parameter and type of the underlying element is not part of the indexer signature. And because indexers do not have user-defined names, indexers are only identified through their signature. An indexer is always an instance member and can never be static. When overriding a virtual indexer, the inherited indexer implementation can be accessed using the base keyword. The following example illustrates:
public class Grid { // Declare the 2-D array that the indexer of this class will access int[,] aGrid = new int[10,20]; // Declare an indexer to access any element in the 2dimensional array public virtual int this[int nRow, int nColumn] { get { return aGrid[nRow, nColumn]; } set { aGrid[nRow, nColumn] = value; } } } public class DiagonalGrid : Grid { // If row and column are equal, then use base implementation, else throw an exception public override int this[int nRow, int nColumn] { get { if (nRow == nColumn) return base[nRow, nColumn]; else throw new ArgumentException( "Row and Columns indices are different" ); } set { if (nRow == nColumn) base[nRow, nColumn] = value; else throw new ArgumentException( "Row and Columns indices are different" ); } } } try { // Create a new instance DiagonalGrid obDG = new DiagonalGrid(); // Assign values obDG[0,0] = 10; // OK. Assigns value obDG[0,1] = 100; // Throws an exception because row != column } catch( Exception ex) { Trace.WriteLine( ex.Message ); }
OPERATORS An operator is a class member that defines what it means to apply an expression operator to an instance of a class. For example, if ob1 and ob2 are two objects, what does ob1 + ob2 mean?
Or what does ob1++ mean? The definition of these operators (binary + in the first case, and unary increment in the second case) is defined by class operators. Declaring an operator takes the following forms: [attributes] [modifiers] type operator overloadable-unaryoperator (type operand) operator-body [attributes] [modifiers] type operator overloadable-binaryoperator (type operand1, type operand2) operator-body [attributes] [modifiers] implicit|explicit operator conv_type_out (conv_type_in operand) operator-body Where [modifiers] can be: • • •
public static extern
and overloadable-unary-operator can be: • • • • • • • •
+ ! ~ ++ -true false (true and false require pair-wise declaration - if one is defined the other must be defined also.)
and overloadable-binary-operator can be: • • • • • • • •
+ * % / & | and many others (review MSDN).
Note the following points about operators: • • •
An operator declaration must include either extern and hence no function body, or both public and static modifiers. The parameters of an operator must all be value parameters. It is a compile-time error for an operator declaration to use ref or out parameters. It is not possible for an operator declared in a derived class to hide an operator declared in a base class, Therefore, the new keyword is never permitted on an operator. This is because operator declarations always require class to participate in the signature of the operator.
From the above definition that there are three categories of overloadable operators (assume T is the type of the containing class):
1. Unary operators 1. Operator definition for ++ and -- must take a single parameter of type T and return T.
2. Operator definition for +,-, ! or ~ must take a single parameter of type T and return any type.
3. Operator definition for true and false must take a single parameter of type T and return bool.
2. Binary operators A binary operator declaration must take two parameters one of which must be the type of the containing class. A binary operator can return any type. Certain binary operators require pair-wise declaration (if one is defined, then the other must be defined as well: • • •
operator== and operator!= operator > and operator < operator >= and operator 0) && (pt.nY > 0)) return true; else return false; } public static bool operator false(Point pt) { // Delegate to operator true if (pt) return true; else return false;
} /* Binary Operators */ // Binary operator + and -: operator declaration must take two parameters of which // at least one must be the type of the containing class. The declaration can return // any type public static Point operator+( Point ptLHS, Point ptRHS ) // LHS: Left-hand-source. RHS:Right-hande-source { // Create and initialize a new point Point pt = new Point(); pt.nX = ptLHS.nX + ptRHS.nX; pt.nY = ptLHS.nY + ptRHS.nX; // return results return pt; } public static Point operator-( Point ptLHS, Point ptRHS ) // LHS: Left-hand-source. RHS:Right-hande-source { // Create and initialize a new point Point pt = new Point(); pt.nX = ptLHS.nX - ptRHS.nX; pt.nY = ptLHS.nY - ptRHS.nX; // return results return pt; } /* Conversion Operators */ // Conversion operator: convert implicitly (no cast required) form PointF to Point public static implicit operator Point (PointF ptF) { return new Point( (int)ptF.fX, (int)ptF.fY ); } // Conversion operator: convert explicitly (cast required) from PointD to Point public static explicit operator Point (PointD ptD) { return new Point( (int)ptD.dX, (int)ptD.dY ); } } /* These classes are used to illustrate conversion operators for Point class */ public class PointF { public float fX, fY; public PointF( float x, float y ) { fX = x; fY = y; } } public class PointD { public double dX, dY; public PointD( double x, double y ) { dX = x; dY = y; } } // Create a new instance Point pt = new Point(1,2); // Test unary operators
pt++; pt--; if (pt) Trace.WriteLine( "pt is true" ); // Test binary operators Point pt1 = new Point( 10, 10 ); Point pt2 = new Point( 20, 20 ); Point pt3 = pt1 + pt2;
// Invokes operator++ // Invokes operator-// Invokes operator true
// Invokes operator+
// Test conversion operators PointF ptF = new PointF( 10.0F, 20.0F ); Point pt4 = ptF; // Invokes operator Point (PointF ptF) PointD ptD = new PointD( 10.0D, 10.0D ); Point pt5 = (Point)ptD; // Invokes operator Point (PointD ptD)
CONSTRUCTORS There are two types of constructors in C#: • •
Instance Constructors Static Constructors
Instance Constructors An instance constructor is a class member that implements actions required to initialized an instance of a class. Declaring a constructor takes the following forms: [attributes] [modifiers] class-name (formal-parameter-list) : base (argument-list) { ... } [attributes] [modifiers] class-name (formal-parameter-list) : this (argument-list) { ... } Where [modifiers] can be: • • • • •
public protected internal private extern
Instance constructors are not inherited. Hence, a class has no instance constructors other than those defined in the class. If a class contains no instance constructor declaration, a default instructor (one that takes no parameter) is provided automatically.
Constructor Initializers In the constructor declaration form, base( argument-list) and this(argument-list) are known as constructor initializers. Constructors initializers are used to invoke other instance constructors immediately before the constructor body as follows: • •
base( argument-list): causes the appropriate instance constructor of the directbase-class to be invoked. this(argument-list) : causes the appropriate instance constructor from the containing class to be invoked.
•
If an instance constructor has no constructor initializers, an instance constructor initializer of the form base() is automatically provided. This ensures that base class constructors are called as appropriate.
Note that constructor initializers are only allowed to access the parameters passed to the instance constructor. The following example illustrates: public class BaseLevel1 { public BaseLevel1() { Trace.WriteLine("BaseLevel1.BaseLevel1()"); } public BaseLevel1(int n) { Trace.WriteLine("BaseLevel1.BaseLevel1(int n)"); } } public class DerivedLevel1 : BaseLevel1 { public DerivedLevel1() Implicitly calls base() { Trace.WriteLine( "DerivedLevel1.DerivedLevel1()"); } public DerivedLevel1( double d ) Implicitly calls base() {
//
//
Trace.WriteLine( "DerivedLevel1.DerivedLevel1(double)"); } public DerivedLevel1( int n ) : base(n) // Calls and passes parameter to base class constructor { Trace.WriteLine( "DerivedLevel1.DerivedLevel1(int)"); } public DerivedLevel1( int n, string s ) : this(n) // Calls and passes parameter to an instance constructor in this class { Trace.WriteLine( "DerivedLevel1.DerivedLevel1(int, string)"); } } // Test2 constructor initializers Trace.WriteLine( "Calling constructor with no argument" ); DerivedLevel1 ob1 = new DerivedLevel1(); Trace.WriteLine( "\nCalling constructor with an int argument" ); DerivedLevel1 ob2 = new DerivedLevel1( 10 ); Trace.WriteLine( "\nCalling constructor with an int argument and a string argument" ); DerivedLevel1 ob3 = new DerivedLevel1( 20, "hello" );
In the example above, the this() form of a constructor initializer in commonly used in conjunction with operator overloading to implement optional instance constructor parameters: /* Optional constructor parameters */ public class Person { // Data members private int nAge; private string strName; // Main constructor public Person ( int age, string name ) { nAge = age; strName = name; } // Overloaded constructors that delegate to the main constructor public Person() : this( 0, "" ) {} public Person( int age ) : this( age, "" ) {} public Person( string name ) : this( 0, name ) {} }
Constructor Execution Upon entry to the instance constructor and before the implicit invocation of the direct-base class constructor, variable initializers of the instance fields are executed (in the order in which they appear in the class). In other words, variable initializers are transformed into assignment statements and these assignment statements are executed before the invocation of the base class instance constructor. For example public class Base { public Base( int n ) { ... } } public class Derived : Base { // Data members
int nX = (int)Math.Sqrt( 100 ); int nY; // Constructors public Derived( int n ) : base(n) { ... } } Derived ob = new Derived( 100 ); When object ob is created:
1. nY is initialized to zero 2. nX is assigned a value of 10 (square root of 100) 3. The base constructor is called. 4. The derived constructor is called.
Private Constructors When a class declares private or protected constructors only it means that: • •
The class cannot be instantiated by clients. The class cannot be derived from.
For example, public class BaseWithPC { private BaseWithPC() { Trace.WriteLine( "BaseWithPC" ); } } public class DerivedFromBaseWithPC : BaseWithPC { public DerivedFromBaseWithPC() { Trace.WriteLine( "DerivedFromBaseWithPC" ); } } // BaseWithPC constructor is private BaseWithPC ob5 = new BaseWithPC();
//
'Primer.BaseWithPC.BaseWithPC()' is inaccessible due to its protection level
DerivedFromBaseWithPC ob6 = new DerivedFromBaseWithPC();
//
'Primer.BaseWithPC.BaseWithPC()' is inaccessible due to its protection level
If BaseWithPC constructor was declared protected instead or private, then the object cannot still be created, but derived classes can derive from it (since the base constructor is now accessible). In other words: // BaseWithPC constructor is protected BaseWithPC ob5 = new BaseWithPC();
//
'Primer.BaseWithPC.BaseWithPC()' is inaccessible due to its protection level
DerivedFromBaseWithPC ob6 = new DerivedFromBaseWithPC();
Static Constructors
//
OK
Recall that an instance constructor is a class member that implements actions required to initialized an instance of a class. A static constructor on the other hand is a class member that implements actions required to initialized a class. In other words, instance constructors initialize instances and static constructors initialize classes. Declaring a static constructor takes the following forms: [attributes] [extern] class-name () { ... } access modifiers and parameters are not permitted
// Note that
The body of a static constructor specified all the statements required to initialize the class. Note that static constructors are: • • •
Cannot inherited. Cannot be called directly. Are parameterless (hence cannot be overloaded.)
The exact timing of when a static constructor is called is implementation-dependent. However, it is subject to the following rules: • • • •
The static constructor of a class executes before any instance of the class is created. The static constructor of a class executes before any of the static members of the class (if any) are referenced. The static constructor of a class executes after the static field initializers (if any) for the class. The static constructor of a class executes at most one time during the lifetime of a program.
The following example illustrates: public class MyClass { // Data members static int nNum = 10; static string strName; double dNum; static MyClass() // Cannot specify access modifiers on a static constructor { Trace.WriteLine( "public static MyClass()" ); strName = "MyClass"; } public MyClass() { Trace.WriteLine( "public MyClass()" ); dNum = 99.99; } } MyClass ob = new MyClass(); before instance constructor
// static constructor called
DESTRUCTORS A destructor is class member that implements actions required to destroy an instance of a class. Declaring a static constructor takes the following forms: [attributes] [extern] class-name () { ... } access modifiers and parameters are not permitted
// Note that
Destructors are similar to static constructors in two areas: • • •
Destructors are not inherited. Cannot be called directly. Destructors are parameterless (hence cannot be overloaded.)
Destructors cannot be invoked directly. It is up to the garbage collector to decide when to invoke the destructor. What happens if an exception is thrown and not caught in a destructor? Destructor execution is terminated and the destructor of the base class is called. If there is no base class, the exception is discarded.
View more...
Comments