Saturday, January 30, 2010

The .NET Framework Class Libraries

The .NET Framework class libraries are monumentally important to providing language interoperability because they allow developers to use a single programming interface to all the functionality exposed by the CLR. If you've ever used more than one dissimilar language in development for Windows, you'll love this feature. In fact, the .NET Framework class libraries are forging a revolutionary trend in compiler development. Before .NET, most compiler writers developed a language with the ability to do most of its own work. Even a language such as C++, which was designed as a scaled-down grouping of functionality to be used in conjunction with a class library, has at least some functionality on its own. However, in the world of .NET, languages are becoming little more than syntactical interfaces to the .NET Framework class libraries.
As an example, let's first take a look at the standard "Hello, World" application in C++ and then compare it to an application that does the same thing in C#: -
#include 

int main(int argc, char* argv[])
{
    cout << "Hello, World!" << endl;
    return 0;
}
Notice that the application first includes a header file with the declaration of the cout function. The application's main function-every C/C++ application's entry point-uses the cout function to write the string "Hello, World" to the standard output device. However, what's important to note here is that you can't write this application in any .NET language without the .NET Framework class libraries. That's right: .NET languages don't even have the most basic compiler features, such as the ability to output a string to the console. Now I know that technically the cout function is implemented in the C/C++ runtime, which is itself a library. However, basic C++ tasks such as string formatting, file I/O, and screen I/O are at least logically considered part of the base language. With C#-or any .NET language for that matter-the language itself has almost no ability to do even the most menial task without the .NET Framework class library.
Let's look at the "Hello, World" example in C# to see what I mean: -
using System;

class Hello
{
    public static void Main()
    {
        Console.WriteLine("Hello, World");
    }
}
So, what does this common set of class libraries mean to you, and is it a good thing? Well, it depends on your vantage point. A common set of class libraries means that all languages, theoretically, have the same capabilities because they all have to use these class libraries to accomplish anything except declaring variables.
One gripe I've seen on discussion boards is, "Why have multiple languages if they all have the same capabilities?" For the life of me, I don't understand this complaint. As someone that has worked in many multilanguage environments, I can attest that there's a great benefit to not having to remember what language can do what with the system and how it does it. After all, our job as developers is to produce code, not to worry about whether a favorite language has this advantage or that advantage.
Another question I've seen frequently is, "If all these .NET languages can do the same thing, why do we need more than one?" The answer relates to the fact that programmers are creatures of habit. Microsoft certainly didn't want to pick one language out of the many available and force millions of programmers to toss out their years of experience in other languages. Not only might a programmer have to become familiar with a new API, he or she might have to master a completely different syntax. Instead, a developer can continue using the language that's best suited for the job. After all, the name of the game is productivity. Changing what doesn't need to be changed is not part of that equation.
NOTE
While in theory the .NET Framework class libraries enable compilers to make all the CLR's functionality available to a language's users, this is not always the case. One point of contention at Microsoft between the .NET Framework class libraries team and the different compiler teams is that although the .NET Framework class libraries team has attempted to expose all its functionality to the different languages, there's nothing-besides meeting minimal CLS standards-that requires the different compiler teams to implement every single feature. When I asked several Microsoft developers about this discrepancy, I was told that instead of each language having access to every exposed bit of .NET Framework functionality, each compiler team has decided to implement only the features that they feel are most applicable to their users. Luckily for us, however, C# happens to be the language that seems to have provided an interface to almost all of the .NET Framework functionality.

The Common Language Runtime

The CLR is the very core of .NET. As the name suggests, it is a run-time environment in which applications written in different languages can all play and get along nicely-otherwise known as cross-language interoperability. How does the CLR provide this cozy environment for cross-language interoperability? The Common Language Specification (CLS) is a set of rules that a language compiler must adhere to in order to create .NET applications that run in the CLR. Anyone, even you or me, who wants to write a .NET-compliant compiler needs simply to adhere to these rules and, voila!, the applications generated from our compilers will run right alongside any other .NET application and have the same interoperability.
An important concept related to the CLR is managed code. Managed code is just code that is running under the auspices of the CLR and is therefore being managed by the CLR. Think of it like this: In today's Microsoft Windows environments, we have disparate processes running. The only rule that applications are required to follow is that they behave well in the Windows environment. These applications are created by using one of a multitude of completely dissimilar compilers. In other words, the applications have to obey only the most general of rules to run under Windows.
The Windows environment has few global rules regarding how the applications must behave in terms of communicating with one another, allocating memory, or even enlisting the Windows operating system to do work on their behalf. However, in a managed code environment, a number of rules are in place to ensure that all applications behave in a globally uniform manner, regardless of the language they were written in. The uniform behavior of .NET applications is the essence of .NET and can't be overstated. Luckily for you and me, these global rules primarily affect the compiler writers.

The Microsoft .NET Platform

The idea behind Microsoft .NET is that .NET shifts the focus in computing from a world in which individual devices and Web sites are simply connected through the Internet to one in which devices, services, and computers work together to provide richer solutions for users. The Microsoft .NET solution comprises four core components: -
  • .NET Building Block Services, or programmatic access to certain services, such as file storage, calendar, and Passport.NET (an identity verification service).
  • .NET device software, which will run on new Internet devices.
  • The .NET user experience, which includes such features as the natural interface, information agents, and smart tags, a technology that automates hyperlinks to information related to words and phrases in user-created documents.
  • The .NET infrastructure, which comprises the .NET Framework, Microsoft Visual Studio.NET, the .NET Enterprise Servers, and Microsoft Windows.NET.
The .NET infrastructure is the part of .NET that most developers are referring to when they refer to .NET. You can assume that any time I refer to .NET (without a preceding adjective) I'm talking about the .NET infrastructure. The .NET infrastructure refers to all the technologies that make up the new environment for creating and running robust, scalable, distributed applications. The part of .NET that lets us develop these applications is the .NET Framework.
The .NET Framework consists of the Common Language Runtime (CLR) and the .NET Framework class libraries, sometimes called the Base Class Library (BCL). Think of the CLR as the virtual machine in which .NET applications function. All .NET languages have the .NET Framework class libraries at their disposal. If you're familiar with either the Microsoft Foundation Classes (MFC) or Borland's Object Windows Library (OWL), you're already familiar with class libraries. The .NET Framework class libraries include support for everything from file I/O and database I/O to XML and SOAP. In fact, the .NET Framework class libraries are so vast that it would easily take a book just to give a superficial overview of all the supported classes.
As a side note (as well as an admission of my age), when I use the term "virtual machine," I don't mean the Java Virtual Machine (JVM). I'm actually using the traditional definition of the term. Several decades ago, before Java was anything more than another word for a dark, hot beverage, IBM first coined "virtual machine." A virtual machine was a high-level operating system abstraction within which other operating systems could function in a completely encapsulated environment. When I refer to the CLR as a kind of virtual machine, I'm referring to the fact that the code that runs within the CLR runs in an encapsulated and managed environment, separate from other processes on the machine.

Polymorphism

The best and most concise definition I've heard for polymorphism is that it is functionality that allows old code to call new code. This is arguably the biggest benefit of object-oriented programming because it allows you to extend or enhance your system without modifying or breaking existing code.
Let's say you write a method that needs to iterate through a collection of Employee objects, calling each object's CalculatePay method. That works fine when your company has one employee type because you can then insert the exact object type into the collection. However, what happens when you start hiring other employee types? For example, if you have a class called Employee and it implements the functionality of a salaried employee, what do you do when you start hiring contract employees whose salaries have to be computed differently? Well, in a procedural language, you would modify the function to handle the new employee type, since old code can't possibly know how to handle new code. An object-oriented solution handles differences like this through polymorphism.-
Using our example, you would define a base class called Employee. You then define a derived class for each employee type (as we've seen previously). Each derived employee class would then have its own implementation of the CalculatePay method. Here's where the magic occurs. With polymorphism, when you have an upcasted pointer to an object and you call that object's method, the language's runtime will ensure that the correct version of the method is called. Here's the code to illustrate what I'm talking about: -
using System;

class Employee
{
    public Employee(string firstName, string lastName,
                    int age, double payRate)
    {
        this.firstName = firstName;
        this.lastName = lastName;
        this.age = age;
        this.payRate = payRate;
    }

    protected string firstName;
    protected string lastName;
    protected int age;
    protected double payRate;

    public virtual double CalculatePay(int hoursWorked)
    {
        Console.WriteLine("Employee.CalculatePay");
        return 42; // bogus value
    }
}

class SalariedEmployee : Employee
{
    public SalariedEmployee(string firstName, string lastName,
                            int age, double payRate)
    : base(firstName, lastName, age, payRate)
    {}

    public override double CalculatePay(int hoursWorked)
    {
        Console.WriteLine("SalariedEmployee.CalculatePay");
        return 42; // bogus value
    }
}


class ContractorEmployee : Employee
{
    public ContractorEmployee(string firstName, string lastName,
                              int age, double payRate)
    : base(firstName, lastName, age, payRate)
    {}

    public override double CalculatePay(int hoursWorked)
    {
        Console.WriteLine("ContractorEmployee.CalculatePay");
        return 42; // bogus value
    }
}

class HourlyEmployee : Employee
{
    public HourlyEmployee(string firstName, string lastName,
                          int age, double payRate)
    : base(firstName, lastName, age, payRate)
    {}

    public override double CalculatePay(int hoursWorked)
    {
        Console.WriteLine("HourlyEmployee.CalculatePay");
        return 42; // bogus value
    }
}

class PolyApp
{
    protected Employee[] employees;

    protected void LoadEmployees()
    {
        Console.WriteLine("Loading employees...");

        // In a real application, we'd probably get this
        // from a database.
        employees = new Employee[3];

        employees[0] = new SalariedEmployee ("Amy", "Anderson", 28, 100);
        employees[1] = new ContractorEmployee ("John", "Maffei", 35, 110);
        employees[2] = new HourlyEmployee ("Lani", "Ota", 2000, 5);

        Console.WriteLine("\n");
    }


    protected void CalculatePay()
    {
        foreach(Employee emp in employees)
        {
            emp.CalculatePay(40);
        }
    }

    public static void Main()
    {
        PolyApp app = new PolyApp();

        app.LoadEmployees();
        app.CalculatePay();
    }
}
Compiling and running this application will yield the following results: -
c:\>PolyApp

Loading employees...

SalariedEmployee.CalculatePay
ContractorEmployee.CalculatePay
HourlyEmployee.CalculatePay
Note that polymorphism provides at least two benefits. First, it gives you the ability to group objects that have a common base class and treat them consistently. In the example above, although technically I have three different object types-SalariedEmployee, ContractorEmployee,and HourlyEmployee-I can treat them all as Employee objects because they all derive from the Employee base class. This is how I can stuff them in an array that is defined as an array of Employee objects. Because of polymorphism, when I call one of those object's methods, the runtime will ensure that the correct derived object's method is called.
The second advantage is the one I mentioned at the beginning of this section: old code can use new code. Notice that the PolyApp.CalculatePay method iterates through its member array of Employee objects. Because this method extracts the objects as implicitly upcasted Employee objects and the runtime's implementation of polymorphism ensures that the correct derived class's method is called, I can add other derived employee types to the system, insert them into the Employee object array, and all my code continues working without me having to change any of my original code! -

Defining Proper Inheritance

To address the all-important issue of proper inheritance, I'll use a term from Marshall Cline and Greg Lomow's C++ FAQs (Addison-Wesley, 1998): substitutability.Substitutability means that the advertised behavior of the derived class is substitutable for the base class. Think about that statement for a moment-it's the single most important rule you'll learn regarding building class hierarchies that work. (By "work," I mean stand the test of time and deliver on the OOP promises of reusable and extendable code.) -
Another rule of thumb to keep in mind when creating your class hierarchies is that a derived class should require no more and promise no less than its base class on any inherited interfaces. Not adhering to this rule breaks existing code. A class's interface is a binding contract between itself and programmers using the class. When a programmer has a reference to a derived class, the programmer can always treat that class as though it is the base class. This is called upcasting. In our example, if a client has a reference to a ContractEmployee object, it also has an implicit reference to that object's base, an Employee object. Therefore, by definition, ContractEmployee should always be able to function as its base class. Please note that this rule applies to base class functionality only. A derived class can choose to add behavior that is more restrictive regarding its requirements and promises as little as it wants. Therefore, this rule applies only to inherited members because existing code will have a contract with only those members.