Type System Additions


The .NET type system is considerably richer than the COM type system and so required a number of concepts to be added to the Visual Basic language. The most important was inheritance, but overloading, namespaces, and name hiding were also significant changes.

Classes

A class in previous versions of Visual Basic is equivalent to a COM coclass, which is a collection of supported interfaces. A coclass does not have a distinct identity ”when creating a coclass, the creator requests one of the interfaces that the coclass implements. It is then possible to request other supported interfaces by requesting the IUnknown interface of the instance. However, in general it is not possible to know what coclass you have in hand given a particular interface pointer.

Previous versions of Visual Basic simplify this situation slightly through the concept of default interfaces. When a class is defined in these versions of Visual Basic, a hidden interface is created that contains all the public members defined in the class. The class then implements that default interface. Because COM lacks the concept of a class as a thing in and of itself, data members of classes have to be exposed as property methods . For example, given the following class definition in Visual Basic 6.0 ( class1.cls ),

 Public Value As Integer  Public Sub PrintValue()    ... End Sub Public Function CalculateValue() As Integer    ... End Function 

the following would have been generated:

  • An interface named _class1 that contained four members: a property get method for Value , a property let method for Value , a method PrintValue , and a method CalculateValue

  • A coclass class1 that implemented _class1

In contrast to COM, the .NET type system invests identity in classes rather than interfaces. A class in .NET is a first-class object with its own identity and storage managed by the runtime environment. Classes can implement interfaces, but a reference ”even to an interface ”always refers to a particular instance of a class, never just an interface on its own. There are many benefits to this scheme (not the least of which is that data members do not have to be expressed as properties), but it does make interoperability between versions of Visual Basic a little more difficult.

As a side note, one incompatibility between COM and .NET that is unavoidable is that the unit of identity changes between the two platforms. For example, given a class class1 that implements an interface interface1 , Listing A.1 will produce two different answers on COM and on .NET.

Listing A.1 module1.bas (VB6)
 Sub Main()     Dim c As Class1     Set c = New Class1           ' VB6 syntax used     Dim i As Interface1     Set i = c                    ' VB6 syntax used     Debug.Print TypeName(i)      ' VB6 syntax used End Sub 

Under COM, the name printed out will be "interface1," because that is the unit of identity (i.e., there is no true concept of "class1"). Under .NET, however, the name printed out will be "class1," because the thing that holds identity is the class, not the interface. No matter what interface a class is cast to, the .NET runtime environment still knows what class the instance is.

The question then becomes how the COM concepts should be mapped to the .NET concepts when attempting to access a COM object from .NET. The simplest answer would be to map a COM coclass to a .NET class and a COM interface to a .NET interface. However, in this case, the tricks that Visual Basic 6.0 played become troublesome in .NET. The problem is that because the unit of identity in COM was the interface, that is what is used when calling methods. So, given a coclass class1 , a method taking a class1 would actually be expressed as taking the default interface, _class1 . This was fine in Visual Basic 6.0, because the compiler hid the distinction. But because both classes and interfaces are first-class types in Visual Basic .NET, this means using COM objects would require a lot of casting if strict type checking (new in Visual Basic .NET) is used. Consider Listings A.2 and A.3.

Listing A.2 Class1.cls (VB6)
 Private Value As Integer Public Function CreateNewInstance() As Class1     CreateNewInstance = New Class1() End Sub 
Listing A.3 test.vb (VBNET)
 Option Strict On Module Test     Sub Main()         Dim x As Class1 = New Class1()         Dim y As Class1 = CType(x.CreateNewInstance(), _                                 Class1)     End Sub End Module 

In Listing A.3, the return value of CreateNewInstance has to be explicitly cast to the interface _Class1 because strict type checking requires that a cast from an interface to a class that supports the interface be explicit (as the instance might be of some other class type).

To avoid forcing the distinction between interfaces and classes on users as soon as they attempt to upgrade code (because it is likely that code will use COM objects such as ADO), the language slightly modified its rules to make this situation more straightforward. Instead of the simple mapping, the .NET runtime environment changes the name of the default interface to the name of the coclass, and appends "Class" onto the end of the name of the coclass. Thus a coclass class1 with a default interface _class1 would be mapped to a class class1Class and an interface class1 .

The result is that a user creating an instance of a COM class will actually appear to be instantiating an instance of the default interface. Under the covers, the compiler then maps this to an instantiation of the coclass. Now the variable is correctly typed, however, and will not require any casts to be assigned to by an API or passed to another API.

Inheritance

Adding inheritance to the Visual Basic .NET language was a relatively straightforward operation. The main set of decisions that had to be made dealt with how explicit to make the various inheritance concepts and what terms to use to refer to them. Because Visual Basic is a language that has a long history that does not include inheritance, we felt it was best to make the most conservative choices in regard to defaults. We also felt that it would be better to choose more descriptive (and more verbose) keywords rather than using C++-style keywords.

Table A.1 lists the keywords defined by the Visual Basic .NET language and their C# equivalents. It is worth noting that to both make a method virtual and override it requires explicit keywords on the part of the developer.

Table A.1. Visual Basic .NET and C# Keywords

VB .NET Keyword

C# Keyword

Overridable

virtual

NotOverridable

final

Overrides

overrides

MustOverride

abstract (on a method)

MustInherit

abstract (on a class)

NotInheritable

sealed

When implementing inheritance, we had to choose what kind of name hiding would be done across the inheritance chain. A robust name hiding scheme was seen as critical to avoiding many of the versioning issues that Visual Basic had with COM. In particular, it was important to be able to "drop in" a new version of a Base Class Library that contained new methods and have code compiled against it still continue to work. The canonical example of this situation would be ASP.NET ”it is desirable to be able to upgrade ASP.NET without requiring all the Web pages on a Web site be recoded.

For simplicity, the default name hiding semantic we initially chose was hide by name. In other words, a member defined with a particular name would hide all members by that name in the base classes. The Shadows keyword was added to the language, but purely for developer awareness ”all members were implicitly marked shadow by name, and omitting Shadows only caused a warning, rather than an error, to be emitted .

This choice, however, presented a problem with overloading, which we were also adding to the language. Given a hide by name semantic, it is not possible for a method to be overloaded across a base class and a derived class, because the derived class members hide the base class members by the same name. At this point it would have been possible to decide that overloading across inheritance was not allowed and be done with it, but this seemed to be a shame. A more desirable outcome would be to allow developers to explicitly state that they wished to overload a method in a base class rather than hide it. To this end, we added the Overloads keyword. The Overloads keyword specifies that a member has a hide by name and signature semantic associated with it rather than a hide by name semantic. As a result, a method will only hide a method with the same name and exact signature, allowing overloading across the inheritance hierarchy (see Listing A.4).

Listing A.4 test.vb (VB.NET)
 Class Base     Public Sub A(ByVal x As Integer)         ...     End Sub     Public Sub B(ByVal x As Integer)         ...     End Sub End Class Class Derived     Inherits Base     Public Shadows Sub A(ByVal y As Double)         ...     End Sub     Public Overloads Sub B(ByVal y As Double)         ...     End Sub     Public Sub C()         A(10)             ' Calls Derived.A         B(10)             ' Calls Base.B     End Sub End Class 

Given that a method could now have one of two separate name-hiding semantics associated with it, the question arose as to which should be the default. We initially chose hide by name as the default, but would it make more sense to choose hide by name and signature as the default? Ultimately, we decided that it did not make more sense because of the implications for overload resolution. Because overload resolution chooses among all the methods in the inheritance hierarchy with the same name (see the "Overloading" section for more details), a method marked as Overloads was vulnerable to changes in the base class.

To explain in more detail, imagine this situation: A base class vendor, Acme, produces a class called Base . Another company, MegaCorp, buys Acme's base class Base and derives a class Derived from it. The class Derived contains a set of methods Foo that are marked Overloads . MegaCorp then builds an end-user application that uses Derived and calls method Foo . After some period of time, Acme releases an upgrade to class Base , adding a method called Foo to the class. MegaCorp purchases the new base class, installs it, and rebuilds Derived and the end-user application. The problem is that because Derived.Foo was marked as Overloads , the method Base.Foo will now be incorporated into the overload resolution for method Foo . If there was an unlucky choice of types for the overloads of Foo , it's possible that the end-user application will silently start calling Base.Foo , even though that method may do something radically different than Derived.Foo does!

The only way to completely solve this issue would be to choose a method of overload resolution similar to the one that C# uses, as discussed in the next section. Barring that, choosing Shadows as the default name-hiding semantic for methods seemed the safest choice. There is still some danger in using Overloads , but the risk can be assumed explicitly by the developer when he or she adds the keyword.

Overloading

Because overloading is a fundamental part of the .NET runtime environment, it was necessary to add it to the Visual Basic .NET language. Visual Basic already had a similar kind of mechanism in optional parameters, but in most cases overloading is a more robust way of accomplishing the same goals. This is especially true given that the values of optional parameters are compiled into the caller rather than staying under the control of the method being called.

As mentioned in the "Inheritance" section, the primary question that had to be answered regarding overloading was how overloading would interact with inheritance. We chose what we thought was the simplest answer to the question, figuring that it would be the most straightforward and understandable: When doing overload resolution on a method, we consider all the methods by the particular name in the inheritance hierarchy at once. This means that the most specific overload is always guaranteed to be chosen .

The downside of this is that, as previously discussed, it creates fragility in a derived class in the face of base class changes. An alternative method was considered that was closer to C#'s method of overload resolution: Consider all the methods overloaded on a name one class at a time, starting with the most derived class and moving to the most base class. This would have solved the fragility issue, but we felt that for Visual Basic programmers the results would be non-intuitive in a number of cases because a less specific overload in a derived class might be chosen over a more specific overload in a base class. Given this possibility, we felt the trade-off was worth it.

One other interesting thing to note about overload resolution in Visual Basic .NET is the way that arguments typed as Object are treated. Although Visual Basic .NET is a strongly typed language, it has a number of features that allow it to be used in a typeless way. The typeless ability of the language comes in handy when doing rapid prototyping or dealing with script-like scenarios. However, overload resolution against arguments typed purely as Object , given a straightforward set of resolution rules, will most likely fail. Because Object is the root type of the type system, a conversion from Object to any other type will be a narrowing conversion and the compiler will be unable to determine a most specific argument from the various choices.

To enable typeless calls to overloaded methods, another rule was added to the Visual Basic .NET overload resolution rules: If overload resolution fails solely because of arguments typed as Object , the resolution of the call is deferred until runtime. In other words, the call is implicitly turned into a late-bound call ( assuming strict semantics are not being used). The late-bound method invocation code has the ability to perform overload resolution at runtime, allowing the resolution to be done on the actual type of the parameter at runtime.

Namespaces

Visual Basic already had an extremely rudimentary concept of a namespace: In previous versions, the name of the project functioned as a namespace for everything declared within it. This allowed for multiple projects to have something named Class1 yet still reference one another. For .NET, the concept just had to be extended to arbitrarily complex namespace schemes. We felt that most users would not want to see Namespace statements in their source code, so we still retained some of the original concept of a project-wide namespace. Essentially, every project can define a namespace that all declarations in the project are implicitly wrapped in. This allows users to define their own namespace hierarchies if they wish, but by default each project gets its own namespace.



Programming in the .NET Environment
Programming in the .NET Environment
ISBN: 0201770180
EAN: 2147483647
Year: 2002
Pages: 146

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net