Example: User-Defined Object Type
User-defined object types are the types that most developers will use to expose their services. These types correspond most closely to classes in many object-oriented languages. As discussed earlier, classes can define different types of members. However, at the fundamental level, only two types of members exist: methods and fields. Abstractions layered over methods include the notions of properties and events. Methods are also specialized to produce constructors, which can be either instance or class constructors (.ctor and .cctor). Once again, on a basic level these constructs are simply methods with added semantics and additional runtime support. This runtime support includes the notion that the class constructor will begin executing before any of its members is used for the first time.7
Let's look an example of how to create a user-defined object type. To make the example more complete, Listing 2.8 creates an object type called Point that implements two interfacesone interface with an event, and one interface with propertiesas well as the user-defined object type. After creating the Point type, the program attaches a listener to the event, writes the properties via interface references that fire the event, and then accesses the properties.
For example, some languages that target the CLR provide little support for object-oriented programming. Instead, they just provide global functions that communicate by passing data as parameters. These languages may still participate in the CLR, but their global functions will normally be mapped to static functions on a "well-known" class, possibly one with a name like Global. Although the way these languages expose their functionality in the CLR is very interesting, the many variations are beyond the scope of this book. The appendices do describe how many languages map their semantics to the CLR.
Listing 2.8 Creating a user-defined object type
using System; namespace ObjectType { public delegate void ADelegate(); public interface IChanged { event ADelegate AnEvent; } public interface IPoint { int X {get; set;} int Y {get; set;} } class Point: IPoint, IChanged { private int xPosition, yPosition; public event ADelegate AnEvent; public int X { get {return xPosition;} set {xPosition = value; AnEvent();} } public int Y { get {return yPosition;} set {yPosition = value; AnEvent();} } } class EntryPoint { static void CallMe() { Console.WriteLine("I got called!"); } static void Main(string[] args) { Point p = new Point(); IChanged ic = p; IPoint ip = p; ic.AnEvent += new ADelegate(CallMe); ip.X = 42; ip.Y = 42; Console.WriteLine("X: {0} Y: {1} ", p.X, p.Y); } } }
The delegate is declared first in the program. It serves as a delegate for functions that accept no arguments and return nothing (void). The returning of void is common with many delegates.
The first interface, which is called IChanged, contains a single event that is of the same type as the delegate. The second interface, which is called IPoint, provides two properties that allow access to two properties known as X and Y. Both properties have get and set methods.
Next comes the definition of the class Point. It would be fair to say that a Point could just as easily be a value type as an object type. Here, however, it is used as a simple abstraction. The class Point inherits from Object, although this relationship is not stated explicitly, and from the two interfaces just defined, IChanged and IPoint. By inheriting from IPoint, for example, the class Point agrees to implement the four abstract methods required for the two properties. The set methods fire the event whenever they are called.
The class EntryPoint provides the entry point for the Main program. This class also provides a static function called CallMe, which matches the prototype of the delegate. The program registers that this method is to be called whenever the event is fired on the Point object named p.
A number of fine points can be emphasized about the program in Listing 2.8. First, only one object is ever created through the activity of the new operator on the first line of Main. The IChanged and IPoint interface types are merely references used to access this Point object. As Point is the exact type for the object, all methods available on the Point class, including the methods in both interfaces, are available through a reference of type Pointin this case, p. This relationship is demonstrated by invoking the methods X and Y in the WriteLine method calls. The interface types can call only the methods in their own interface, even though the object has more functionality. Listing 2.8 produces the following output:
I got called! I got called! X: 42 Y: 42