Features of the Common Language Specification

Whereas CTS specifies the types that are available to be used by programs and applications in the .NET Framework, the Common Language Specification specifies how those types are to be used in a consistent manner to ensure compatibility with other languages. This section discusses the features that are available to all languages that support the Common Language Specification.

Naming

Because it is impractical to program using just numbers, names are given to types, values, instances, and so forth to identify and distinguish them. If the names become obscure and can no longer be relied upon to correctly and uniquely identify a programming element, then it might be better to program with just a sequence of numbers. To avoid this confusion, the CLS has set up some naming guidelines to eliminate name conflict and confusion if the CLS consumer (the programmer) and the CLS provider (the compiler, system tools, and so forth) adheres to them. The following sections discuss valid characters within a name, the scope of a name, and some general guidelines for naming.

Valid Characters

Identifiers within an assembly follow Annex 7 of Technical Report 15 of the Unicode Standard 3.0 (ISBN 0-201-61633-5) found at http://www.unicode.org/unicode/reports/tr15/tr15-18.html. Because symbol comparisons are done on a bit-by-bit basis, the identifier must be normalized so that the identifier cannot be represented in multiple different ways all having the same appearance on a display. Specifically, identifiers support normalization form C. In addition, it is not CLS compliant to have two identifiers differ by just case.

Name Scope

Names are collected into groups called scopes. For a name to be unique or to uniquely identify an element, it has to be qualified and have a name and a scope. Assemblies provide a scope for types, and types provide a scope for names. The following code provides an example of two names that are the same but in a different scope:

public struct A
{
    public int a;
    public int b;
}
public struct B
{
    public int a;
    public int b;
}

The field names a and b in each type are unique because they are qualified by the type. That is to say, field a in type B is different from field a in type A. If these types were part of a different assembly, then the types would be different because the assembly would qualify the types.

The CLS requires that each CLS-compliant language provide a mechanism that allows the use of reserved keywords as identifiers. For C#, this mechanism is with the @ prefix. Using this identifier, you can do the following:

int @int = 4;
Console.WriteLine("Int: {0} ", @int);

This allows the use of a keyword as an identifier.

Important Naming Guidelines

Although the CLS supports a broad range of names as identifiers, not all languages support all of the names that are possible as dictated by the CLS. In addition, certain guidelines increase the readability of your code. Some important naming guidelines are as follows:

  • Use one of the following capitalization styles:

    Pascal Case—The first letter and each subsequent concatenated word is capitalized.

    Camel Case—The first letter is lowercase and each subsequent concatenated word is capitalized.

    Uppercase—The whole identifier is capitalized. Table 2.1 provides some recommendations as to when to use each of these capitalization styles.

    Table 2.1. Capitalization Styles
    IdentifierCapitalization Style
    ClassPascal
    Enum TypePascal
    Enum ValuePascal
    EventPascal
    Read-Only Static FieldPascal
    InterfacePascal (prefix with an “I”)
    MethodPascal
    NamespacePascal
    ParameterCamel
    PropertyPascal
    Protected Instance FieldCamel
    Public Instance FieldPascal

  • Avoid case sensitivity. Some languages that are not case sensitive cannot distinguish between two identifiers that differ only in case. Therefore, avoid relying on case to differentiate types and values. Avoid case sensitivity in namespaces:

    namespace CLRUnleashed;
    namespace clrUnleashed;
    
  • Avoid case sensitivity with parameter names:

    void MyMethod(string aa, string AA)
    
  • Avoid case sensitivity with type names:

    CLRUnleashed.Complex c;
    CLRUnleashed.COMPLEX c;
    
  • Avoid case sensitivity with property names:

    int Property { get, set} ;
    int PROPERTY { get, set} ;
    
  • Avoid method names that differ only by case:

    void Compute();
    void compute();
    
  • It is not CLS compliant to have the same name for a method and a field in a type.

  • Don't use abbreviations or contractions as part of an identifier. For example, use GetDirectory rather than GetDir.

  • Avoid using abbreviations that are not generally accepted in the computing field or in the field where the symbols will be exposed. Wherever possible, use these abbreviations to replace lengthy phrases. For example, use UI to replace User Interface and SQL to replace Structured Query Language.

  • Use Pascal or Camel casing as appropriate for abbreviations that are longer than two characters. For identifiers that are two characters or less, capitalize all characters.

  • Avoid using words that conflict with commonly used .NET Framework class library names and language keywords.

  • Don't use names that describe a type. Use this:

    void Write(double value)
    

    rather than this:

    void Write(double doubleValue)
    
  • Use namespaces with the following format:

    Company.Technology[.Feature][.Design]
    
  • Use a noun or noun phrase to name a class.

  • Do not use a type prefix such as CFileStream. Use FileStream instead.

  • Do not use an underscore character.

  • Where appropriate, if you are deriving from a class, use the name of the base class as the last word in the class name. For example, ApplicationException is derived from Exception. Use CustomAttribute for a custom class that is derived from Attribute.

  • Don't use Enum as part of the name of an Enum type.

  • Use a singular name for most Enum types. Use plural for Enum types that are bit fields (FlagsAttribute attached to the Enum definition).

  • Use the EventHandler suffix for the name of all event handlers.

  • Use the EventArgs suffix for the name of all event argument classes.

  • Two parameters should be passed to an event handler. The first should be named sender (the object that sent the event), and the second should be named e (the XXXEventArg that is associated with the event).

  • Events that can be canceled or occur “before” and “after” should be named as complementing pairs. The “before” should indicate the event is occurring but not complete (as in Closing). The “after” should indicate that the event action is complete (as in Closed). Avoid using BeforeXXX and AfterXXX.

Member Access

You can modify the code snippet from the “Name Scope” section to include access to type A:

public struct A
{
    public int a;
    public int b;
}
public struct B
{
    public int a;
    public int b;
    void AccessA()
    {
        A At = new A();
        At.a = 0;
    }
}

Because type A only has instance fields, you need to create an instance of A. You can create an instance of A because this type is visible. You can access a because that field has been made accessible to all through the use of the public keyword. In general, member access is determined by three criteria:

  • Type visibility

  • Member accessibility

  • Security constraints

Type Visibility

A type falls into one of three categories with respect to visibility:

  • Exported—A type declares that it is allowed to be exported. The configuration of the assembly determines whether the type is actually visible. With C#, a type can be not public (default), which means it is not visible outside the enclosing scope; internal, which means that it is visible only to the assembly; public, which means it is visible outside of the assembly; or it is exported or exporting is allowed.

  • Not exported—Here, exporting is explicitly disallowed.

  • Nested—The enclosing type determines the visibility of a type that is nested. A nested type is part of the enclosing type, so it has access to all of the enclosing type's members.

Member Accessibility

You can control the accessibility of each member of a type through the following supported levels:

  • Compiler-controlled—This level of accessibility allows access only via a compiler. Members who have this level of accessibility usually support a particular language feature.

  • Private—Private members are not accessible outside of the enclosing type. C# has a private keyword to denote this level of accessibility.

  • Family—Family members are accessible to the enclosing type and types that are inherit from it. In C#, this level of accessibility is obtained with the protected keyword.

  • Assembly—Assembly access means that access is granted only to referents in the same assembly that contains the implementation of the type. In C#, this level of accessibility is obtained with the internal keyword.

  • Family-and-Assembly—This level of accessibility means that the member is accessible only to referents that qualify for both Family and Assembly access.

  • Family-or-Assembly—This level of accessibility means that the member is accessible only to referents that qualify for either Family or Assembly access. In C#, this level of accessibility can be specified by using protected internal.

  • Public—This is accessible to all referents. C# has a public keyword to denote this level of accessibility.

In general, the preceding accessibility levels have two restrictions:

  • Members of an interface are public.

  • If a type overrides a virtual member, then it can make the accessibility of a member greater; it cannot, however, further restrict accessibility. For example, the implementation of an interface method cannot be made private. Because the interface method is public, the implementation cannot be more restrictive.

Security Constraints

Access to members can also be controlled through explicit security attributes or programmatic security permissions. Security demands are not part of a type, but they are attached to a type; therefore, they are not inherited. Accessibility security demands fall into two categories:

  • Inheritance demand—This security attribute can be attached to either a type or a non-final virtual method. If it is attached to a type, then any type that attempts to inherit from it must have the requested security permissions. If it is attached to a non-final virtual method, then any attempt to override the method must first pass the security check. You can attach the inheritance demand security permission as follows:

    [CustomPermissionAttribute(SecurityAction.InheritanceDemand)]
    public class MyClass
    {
        public MyClass()
        {
        }
        public virtual void Message()
        {
            Console.WriteLine("This is a message from MyClass");
        }
    }
    

    This attaches a custom permission (implementation not included) to the class.

  • Reference demand—When a reference demand is placed on an item, the requested security permissions must be in place to resolve the item.

You will learn more about security in Chapter 16, “.NET Security.”

Type Members

Different languages access members of a type in different ways. What is common to all languages is some sort of access control and support for inheritance.

A derived type inherits all of the non-static fields of the base type. Static fields are not inherited.

A derived type inherits all of the instance methods and virtual methods of the base class. As with fields, the static methods are not inherited. A derived class can hide an instance or virtual method by using the new keyword. This causes the derived method to be invoked instead of the base class method. However, if the derived class is cast to the base class, the base class version of the hidden method is called. Therefore, it is hidden, not overridden.

If a method is marked in the base class as virtual and the derived class uses the override keyword, then the derived class method replaces the base class method and is not available. Listing 2.7 shows how to override a method in C#. The complete source for this listing is available in the MethodHideOverrideCS directory.

Listing 2.7. Overriding and Hiding a Method in C#
class A
{
    public virtual void Message()
    {
        Console.WriteLine("This is a message from A");
    }
}
sealed class B: A
{
    public override void Message()
    {
        Console.WriteLine("This is a message from B");
    }
}
class C: A
{
    // methodhideoverride.cs(21,15): warning CS0114:
    // 'CLRUnleashed.C.Message()' hides inherited member
    // 'CLRUnleashed.A.Message()'. To make the current
    // member override that implementation, add the
    // override keyword. Otherwise, add the new keyword.
    // public void Message()
    public new void Message()
    {
        Console.WriteLine("This is a message from C");
    }
}
. . .
 Console.WriteLine("---- A method");
A a = new A();
a.Message();
Console.WriteLine("---- B method");
B b = new B();
b.Message();
Console.WriteLine("---- Cast B to A method");
((A)b).Message();

// Hide a method
Console.WriteLine("---- C method");
C c = new C();
c.Message();
Console.WriteLine("---- Cast C to A method");
((A)c).Message();

A method can be marked as final, which prevents a derived class from overriding it. It is also possible, as noted under the “Security Constraints” section, to restrict the ability of overriding a method by demanding a security permission. With C#, you can mark an entire class as sealed. C# does not support a final keyword or the equivalent functionality, but JScript does. Listing 2.8 shows an example of using final and hide keywords to control how to override a method. The complete source for this sample is in the MethodOverrideJS directory.

Listing 2.8. Using final in JScript
class A
{
   function Message() { print("This is a message from A") } ;
}
class B extends A
{
   final override function Message() { print("This is a message from B") } ;
}

class C extends A
{
   hide function Message() { print("This is a message from C") } ;
}
class D extends B
{
   // MethodHideOverrideJS.jsc(17,4) : error JS1174: Method matches a
   // non-overridable method in a base class. Specify 'hide' to suppress
   // this message
   // override function Message() { print("This is a message from D") } ;
   hide function Message() { print("This is a message from D") } ;
}

var AInstance : A = new A
var BInstance : B = new B
var BAInstance : A = new B
var CInstance : C = new C
var CAInstance : A = new C
var DInstance : D = new D
var DBInstance : B = new D

print("---- A");
AInstance.Message();
print("---- B");
BInstance.Message();
print("---- B -> A");
BAInstance.Message();
print("---- C");
CInstance.Message();
print("---- C -> A");
CAInstance.Message();
print("---- D");
DInstance.Message();
print("---- D -> B");
DBInstance.Message();

VB provides this functionality as well with Overrides and NotOverridable. You can find a complete source for an example using these keywords in the MethodOverrideVB directory. From that source is a function that looks like Listing 2.9.

Listing 2.9. Using NotOverridable in VB
Class B
    Inherits A
    Public NotOverridable Overrides Sub Message()
        Console.WriteLine("This is a message from B")
    End Sub
End Class

Now you can compile this program and look at the output with ILDasm. You will see the output reproduced in Listing 2.10.

Listing 2.10. ILDasm Listing of NotOverridable VB Method
.method public final virtual instance void
        Message() cil managed
{
  // Code size       14 (0xe)
  .maxstack  8
  IL_0000:  nop
  IL_0001:  ldstr      "This is a message from B"
  IL_0006:  call       void [mscorlib]System.Console::WriteLine(string)
  IL_000b:  nop
  IL_000c:  nop
  IL_000d:  ret
}  // end of method B::Message

Notice on the first line that one of the attributes of this Message method in the B class is that it is final. Therefore, NotOverridable in VB translates directly to the IL final attribute.

VC++ with managed extensions mimics the functionality of C# with regard to overriding methods. The managed extensions for VC++ don't have a final keyword, but like C#, a __sealed keyword can prevent an entire class from being inherited.

Properties

Properties provide a means to access private state in an object without exposing the state directly. If you are tempted to expose a field as public, consider using a property instead. Properties are collections of methods—usually a read method and a write method (get and set). You don't have to provide both. If you want to have a read-only property, then just supply the get portion of the property. Properties give you the flexibility of a method with the syntactical sugar that makes direct access to a field so tempting. Like all of the other features discussed in this section, properties are a feature of the Common Language Specification. Therefore, they are available in most languages that support the CLS. Listing 2.11 shows one possible implementation of properties. The complete source for this listing is in the PropertiesCS directory.

Listing 2.11. Properties in C#
public struct Point
{
    private int x;
    private int y;
    public Point(int x, int y)
    {
        this.x = x;
        this.y = y;
    }
    public int X
    {
        get
        {
            return x;
        }
        set
        {
            x = value;
        }
    }
    public int Y
    {
        get
        {
            return y;
        }
        set
        {
            y = value;
        }
    }
    public Point Coordinate
    {
        get
        {
            return this;
        }
        set
        {
            x = value.x;
            y = value.y;
        }
    }
}
. . .
Point p = new Point(1, 2);
Console.WriteLine("X: {0}  Y: {1} ", p.X, p.Y);

As you can see, three properties exist: one for the X coordinate, one for the Y coordinate, and one returning the complete structure. Each of the properties has a get and a set method, so each is readable and writeable. Listing 2.12 shows how to implement properties in VB. The complete source for this listing is in the PropertiesVB directory.

Listing 2.12. Properties in VB
Class Point
    Private xcoordinate As Integer
    Private ycoordinate As Integer
    Sub New(ByVal x As Integer, ByVal y As Integer)
        xcoordinate = x
        ycoordinate = y
    End Sub
    Public Property X() As Integer
        Get
            Return xcoordinate
        End Get
        Set(ByVal Value As Integer)
            xcoordinate = Value
        End Set
    End Property
    Public Property Y() As Integer
        Get
            Return ycoordinate
        End Get
        Set(ByVal Value As Integer)
            ycoordinate = Value
        End Set
    End Property
End Class
Sub Main()
    Dim p As Point = New Point(1, 2)
    Console.WriteLine("---- Point " & p.X & "," & p.Y)
End Sub

Listing 2.13 shows how to implement properties in JScript. The source for this listing is in the PropertiesJS directory.

Listing 2.13. Properties in JScript
class Point
{
   // These variables are not accessible from outside the class.
   private var x: int;
   private var y: int;

   // Set the initial favorite color with the constructor.
   function Point(inputX : int, inputY : int)
   {
      x = inputX;
      y = inputY;
   }

   // Define an accessor to get the X coordinate
   function get X() : int {
      return x;
   }
   // Define an accessor to set the X coordinate
   function set X(inputX : int) {
      x= inputX;
   }
   // Define an accessor to get the Y coordinate
   function get Y() : int {
      return y;
   }
   // Define an accessor to set the X coordinate
   function set Y(inputY : int) {
      y = inputY;
   }
}

var here : Point = new Point(1, 2);
print("Here is " + here.X + "," + here.Y)

Looking at the compiled JScript assembly with ILDasm, you can see how properties are not a feature of any particular language; rather, they are components of the .NET Framework and the CLS. Listing 2.14 shows one of the properties that is exposed in the JScript assembly.

Listing 2.14. ILDasm Listing of the X Coordinate Property from the JScript Assembly
.property int32 X()
{
  .get instance int32 Point::get_X()
  .set instance void Point::set_X(int32)
}  // end of property Point::X

Listing 2.14 reveals that properties are simply a collection and mapping of methods. The implementation for the X property is in the get_X and the set_X methods. The compiler prevents direct access to get_X and set_X.

Events

Events are a way of safely specifying a callback function.

Note

events and delegates are covered in more detail in Chapter 14.


Listing 2.15 shows how to implement events in VB. This sample adds events to the property source of Listing 2.12. The complete source for this program is in the EventsVB directory.

Listing 2.15. Implementing and Using Events with VB
Public Event PointChanged(ByVal x As Integer, ByVal y As Integer)
Sub New(ByVal x As Integer, ByVal y As Integer)
    xcoordinate = x
    ycoordinate = y
End Sub
Public Property X() As Integer
    Get
        Return xcoordinate
    End Get
    Set(ByVal Value As Integer)
        xcoordinate = Value
        RaiseEvent PointChanged(xcoordinate, ycoordinate)
    End Set
End Property
...
Sub EventHandler(ByVal x As Integer, ByVal y As Integer)
    MessageBox.Show("Point changed " & CStr(x) & "," & CStr(y))
End Sub
Sub Main()
    Dim p As New Point(1, 2)
    AddHandler p.PointChanged, AddressOf EventHandler
    p.X = 5
End Sub

The event handler signature is provided by the following:

Public Event PointChanged(ByVal x As Integer, ByVal y As Integer)

The handler is the EventHandler function. (Notice that it has the same signature as the Event declaration.) The handler is registered with the following:

AddHandler p.PointChanged, AddressOf EventHandler

When a Point is changed, an event is raised:

RaiseEvent PointChanged(xcoordinate, ycoordinate)

When the event is raised, a MessageBox is displayed showing the new coordinates. That is triggered with the p.X = 5 statement.

Arrays

Most languages that support the CLS support arrays; however, with C#, JScript, and VB, arrays are always zero based and the lower bound is always zero. The following code shows how to initialize a vector and a two-dimensional array with C#.

int [] avec = new int[5] {1, 2, 3, 4, 5} ;
int [,] aarray = new int[2,2] {{1,2} ,{3,4} } ;

Again, based on the base class library class System.Array, VB also supports vectors and arrays. The following is a small set of code to initialize a one- and a two-dimensional array in VB:

Dim vec() As Integer = New Integer() {1, 2, 3, 4, 5}
Dim array(,) As Integer = New Integer(,) {{0, 1} , {2, 3} }

The following initializes two arrays using JScript.

var vec : int[] = [1,2,3,4,5];
var arr : int  [][] = [ [0, 1], [2, 3] ];

Enumerations

C# directly supports the types that are derived from System.Enum as follows:

enum MathOperations
{
    Add,
    Subtract,
    Multiply,
    Divide
}
. . .
MathOperations mo = MathOperations.Add;
Console.WriteLine("MathOperation: {0} ", mo);

VB also supports types that are derived from System.Enum:

Public Enum MathOperations
    Add
    Subtract
    Multiply
    Divide
    Invalid = -1
End Enum

The closest that JScript has to enumerators is what is termed as an object literal. An object literal allows a programmer to give a name to a number similar to an enum, but it is not derived from System.Enum and the type does not have the methods associated with System.Enum. An example of an object literal is as follows:

dd:1, Subtract:2, Multiply:3, Divide:4 } ;

Exceptions

Exceptions are the error-handling mechanism within the .NET Framework. Exceptions are covered in more detail in Chapter 15, “Using Managed Exceptions to Effectively Handle Errors.” Listing 2.16 shows how to catch an exception with C#. The complete source for this sample is in the ExceptionsCS directory.

Listing 2.16. Throwing and Catching an Exception with C#
try
{
    int a = 1;
    int b = 0;
    int c = a/b;
    Console.WriteLine("Result: {0} ", c);
}
catch(Exception e)
{
    Console.WriteLine(e);
}

The C# code in Listing 2.16 catches a divide by zero exception thrown by the runtime. Listing 2.17 shows an example of using exceptions with VB. The complete source for this sample is in the ExceptionVB directory.

Listing 2.17. Throwing and Catching an Exception with VB
Function TestError()
    Err.Raise(vbObjectError, "TestWidth", _
              "This is a test error message.")
End Function

Sub Main()
    Dim a As Integer
    Dim b As Integer
    Dim c As Integer
    a = 1
    b = 0
    Try
        ' Use integer division
        c = a  b
    Catch ex As Exception
        Console.WriteLine(ex.Message)
    End Try
    Try
        TestError()
    Catch ex As Exception
        Console.WriteLine(ex.Message)
    End Try
End Sub

Notice that with this code, you have to specifically use integer division to generate a divide by zero error. In addition, this sample shows how to “throw” an exception using the VB Err object. If you examine the compiled code of Listing 2.17 with a tool such as ILDasm, you can see that a VB Exception translates directly into System.Exception. Listing 2.18 shows an example of the exception-handling mechanism in JScript. The complete source for this sample is in the ExceptionJS directory.

Listing 2.18. Throwing and Catching an Exception with JScript
try {
    var A : int = 1, B = 0, C;
    C = A/B;
    print("C = " + C);
    throw "This is an error";
}  catch(e) {
    print("Catch caught " + e);
}  finally {
    print("Finally is running...");
}

You would expect that this attempt at divide by zero would fail with an exception. Instead, the print statement in Listing 2.18 simply prints “Infinity”. You don't actually get an exception until you specifically throw the exception with a string message. When this JScript code is examined with ILDasm, you can see that the exception being caught (and thrown) is directly connected with System.Exception. In addition, the exception framework is directly connected with try/catch/finally, which is part of the CLS.

Custom Attributes

Attributes are part of the metadata that is associated with any code that is run in the .NET Framework. Metadata is covered in more detail in Chapter 4, and attributes are covered more specifically in Chapter 17, “Reflection.” Listing 2.19 shows how to define a custom attribute with VB. The complete source for this listing is in the AttributesVB directory.

Listing 2.19. User Defined Custom Attributes with VB
<AttributeUsage(AttributeTargets.Class)> _
Class CustomAttribute
    Inherits System.Attribute

    'Declare two private fields to store the property values.
    Private msg As String

    'The Sub New constructor is the only way to set the properties.
    Public Sub New(ByVal _message As String)
        msg = _message
    End Sub
    Public Overridable ReadOnly Property Message() As String
        Get
            Return msg
        End Get
    End Property
End Class

' Apply the custom attribute to this class.
<Custom("Hello World!")> _
Class MessageClass
    ' Message
    Private msg As String
    Sub New()
        Dim Attr As Attribute
        Dim CustAttr As CustomAttribute
        Attr = GetCustomAttribute(GetType(MessageClass), _
                                  GetType(CustomAttribute), False)
        CustAttr = CType(Attr, CustomAttribute)
        If CustAttr Is Nothing Then
            msg = "The attribute was not found."
        Else
            'Get the label and value from the custom attribute.
            msg = "The attribute label is: " & CustAttr.Message
        End If
    End Sub
    Sub Message()
        Console.WriteLine(msg)
    End Sub
End Class

Sub Main()
    Dim m As New MessageClass()
    m.Message()
End Sub

The code in Listing 2.19 shows how to define a class that derives from System.Attribute and defines a custom attribute that can be attached to any class. The only property of this simple attribute class is a message that the class can discover at runtime. Here, the string that is associated with the message is simply cached and written out to the console with a call to the Message method.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.12.36.30