Chapter 2
The Common Language Runtime
What's in this chapter?
Framework Profiles and Platforms
Elements of a .NET Application
Understanding the Common Language Runtime
Memory Management
Namespaces
The My Keyword
You started with a quick jump straight into getting your hands on Visual Studio. Most developers want to feel something, but before you start diving into syntax this chapter is going to take a look at the bigger picture of how .NET runs in relation to the operating system (OS). While at really low levels for graphics some implementations of the common language runtime (CLR) and the OS may be indistinguishable, at its core the CLR provides the environment in which your application runs.
The architects of .NET realized that all procedural languages require certain base functionality. For example, many languages ship with their own runtime that provides features such as memory management, but what if, instead of each language shipping with its own runtime implementation, all languages used a common runtime? This would provide languages with a standard environment and access to all of the same features. This is exactly what the CLR provides.
The CLR manages the execution of code on the .NET platform. Its common features provide support for many advanced features, including operator overloading, implementation inheritance, threading, and the ability to marshal objects. Building such features is not trivial. The CLR enabled Microsoft to concentrate on building this plumbing one time and then reuse it across different programming languages. As a result the runtime environment used by Visual Basic is the equal of every other .NET language, with the CLR eliminating many of the shortcomings of the previous versions of Visual Basic.
Visual Basic developers can view the CLR as a better Visual Basic runtime. However, this runtime, unlike the old standalone Visual Basic runtime, is common across all of .NET regardless of the underlying operating system. Thus, the functionality exposed by the CLR is available to all .NET languages; more important, all of the features available to other .NET languages via the CLR are available to Visual Basic developers. Additionally, as long as you develop using managed code — code that runs in the CLR — you'll find that it doesn't matter whether your application is installed on a Windows XP client, a Vista client, or a Windows 7 client; your application will run. The CLR provides an abstraction layer separate from the details of the operating system.
This chapter gets down into the belly of the application runtime environment — not to examine how .NET enables this abstraction from the operating system, but instead to look at some specific features related to how you build applications that run against the CLR. This includes an introduction to several basic elements of working with applications that run in the CLR, including the following:
Platform | Versions |
.NET Framework | .NET Framework 4 and later (default) .NET Framework 4, update 4.0.3 or later .NET Framework 4.5 |
Silverlight | Silverlight 4 and later (default) Silverlight 5 |
Windows Phone | Windows Phone 7 and later (default) Windows Phone 7.5 and later Windows Phone 8 |
.NET for Metro style apps | .NET Framework for Metro style (default) |
Xbox 360 | Not currently selected by default |
Method | Description |
Boolean Equals(Object) | Used to test equality with another object. Reference types should return True if the Object parameter references the same object. Value types should return True if the Object parameter has the same value. |
Int32 GetHashCode() | Generates a number corresponding to the value of an object. If two objects of the same type are equal, then they must return the same hash code. |
Type GetType() | Gets a Type object that can be used to access metadata associated with the type. It also serves as a starting point for navigating the object hierarchy exposed by the Reflection API (discussed shortly). |
String ToString() | The default implementation returns the fully qualified name of the object's class. This method is often overridden to output data that is more meaningful to the type. For example, all base types return their value as a string. |
Metadata is the information that enables components to be self-describing. It describes many aspects of .NET components including classes, methods, and fields, and the assembly itself. Metadata is used by the CLR to facilitate behavior, such as:
.NET refines the use of metadata within applications in three significant ways:
Because all metadata associated with a .NET component must reside within the file that contains the component, no global registration of components is required. When a new component is copied into an application's directory, it can be used immediately. Because the component and its associated metadata cannot become out of sync, upgrading the component is not an issue.
.NET makes a much better distinction between attributes that should be set at compile time and those that should be set at runtime. For example, whether a .NET component is serializable is determined at compile time. This setting cannot be overridden at runtime.
Attributes are used to decorate entities such as assemblies, classes, methods, and properties with additional information. Attributes can be used for a variety of purposes. They can provide information, request a certain behavior at runtime, or even invoke a particular behavior from another application. An example of this can be demonstrated by using the Demo class defined in the following code block (code file: ProVB_Attributes\Module1.vb):
Module Module1 <Serializable()> Public Class Demo <Obsolete("Use Method2 instead.")> Public Sub Method1() ' Old implementation … End Sub Public Sub Method2() ' New implementation … End Sub End Class Public Sub Main() Dim d = New Demo() d.Method1() End Sub End Module
Create a new console application for Visual Basic by selecting File ⇒ New Project and selecting Windows Console Application and then add a class into the file module1 by copying the previous code into Module1. A best practice is to place each class in its own source file, but in order to simplify this demonstration, the class Demo has been defined within the main module.
The first attribute on the Demo class marks the class with the Serializable attribute. The base class library will provide serialization support for instances of the Demo type. For example, the ResourceWriter type could be used to stream an instance of the Demo type to disk.
Method1 is prefaced by the Obsolete attribute. Method1 has been marked as obsolete, but it is still available. When a method is marked as obsolete, it is possible to instruct Visual Studio to prevent applications from compiling. However, a better strategy for large applications is to first mark a method or class as obsolete and then prevent its use in the next release. The preceding code causes Visual Studio to display a warning if Method1 is referenced within the application, as shown in . Not only does the line with Method1 have a visual hint of the issue, but a warning message is visible in the Error List, with a meaningful message on how to correct the issue.
If the developer leaves this code unchanged and then compiles it, the application will compile correctly.
Sometimes you might need to associate multiple attributes with an entity. The following code shows an example of using both of the attributes from the previous example at the class level:
<Serializable(), Obsolete("No longer used.", True)> Public Class Demo ' Implementation … End Class
Note in this case the Obsolete attribute has been modified to cause a compilation error by setting its second parameter to True. As shown in , the compilation fails.
Attributes play an important role in the development of .NET applications, particularly XML Web Services. As you'll see in Chapter 11, the declaration of a class as a Web service and of particular methods as Web methods are all handled through the use of attributes.
Leveraging the presence of metadata throughout assemblies and classes, the .NET Framework provides the Reflection API. You can use the Reflection API to examine the metadata associated with an assembly and its types, even to examine the currently executing assembly or an assembly you would like to start while your application is running.
The Assembly class in the System.Reflection namespace can be used to access the metadata in an assembly. The LoadFrom method can be used to load an assembly, and the GetExecutingAssembly method can be used to access the currently executing assembly. The GetTypes method can then be used to obtain the collection of types defined in the assembly.
It's also possible to access the metadata of a type directly from an instance of that type. Because every object derives from System.Object, every object supports the GetType method, which returns a Type object that can be used to access the metadata associated with the type.
The Type object exposes many methods and properties for obtaining the metadata associated with a type. For example, you can obtain a collection of properties, methods, fields, and events exposed by the type by calling the GetMembers method. The Type object for the object's base type can also be obtained by calling the DeclaringType property. Reflection is covered in more detail in Chapter 17.
Within this window, select File ⇒ Open. Open mscorlib.dll, which should be located in your system directory with a default path of C:\Windows\Microsoft.NET\Framework\v4.0.30319\mscorlib.dll.
Once mscorlib.dll has been loaded, ILDasm will display a set of folders for each namespace in this assembly. Expand the System namespace, then the ValueType namespace, and finally double-click the Equals method.
shows the IL for the Equals method. Notice how the Reflection API is used to navigate through the instance of the value type's fields in order to determine whether the values of the two objects being compared are equal.
The IL Disassembler is a useful tool for learning how a particular module is implemented, but it could jeopardize your company's proprietary logic. After all, what's to prevent someone from using it to reverse-engineer your code? Fortunately, Visual Studio 2012, like previous versions of Visual Studio, ships with a third-party tool called an obfuscator. The role of the obfuscator is to ensure that the IL Disassembler cannot build a meaningful representation of your application logic.
A complete discussion of the obfuscator that ships with Visual Studio 2012 is beyond the scope of this chapter, but to access this tool, select the Tools menu and choose PreEmptive Dotfuscator and Analytics. The obfuscator runs against your compiled application, taking your IL file and stripping out many of the items that are embedded by default during the compilation process.
This is where the power of the GC really shines. Before the CLR reaches a point where it is unable to allocate memory on the managed heap, the GC is invoked. The GC not only collects objects that are no longer referenced by the application, but also has a second task: compacting the heap. This is important, because if the GC only cleaned up objects, then the heap would become progressively more fragmented. When heap memory becomes fragmented, you can wind up with the common problem of having a memory allocation fail—not because there isn't enough free memory, but because there isn't enough free memory in a contiguous section of memory. Thus, not only does the GC reclaim the memory associated with objects that are no longer referenced, it also compacts the remaining objects. The GC effectively squeezes out all of the spaces between the remaining objects, freeing up a large section of managed heap for new object allocations.
The GC uses a concept known as generations, the primary purpose of which is to improve its performance. The theory behind generations is that objects that have been recently created tend to have a higher probability of being garbage-collected than objects that have existed on the system for a longer time.
To understand generations, consider the analogy of a mall parking lot where cars represent objects created by the CLR. People have different shopping patterns when they visit the mall. Some people spend a good portion of their day in the mall, and others stop only long enough to pick up an item or two. Applying the theory of generations to trying to find an empty parking space for a car yields a scenario in which the highest probability of finding a parking space is a function of where other cars have recently parked. In other words, a space that was occupied recently is more likely to be held by someone who just needed to quickly pick up an item or two. The longer a car has been parked, the higher the probability that its owner is an all-day shopper and the lower the probability that the parking space will be freed up anytime soon.
Generations provide a means for the GC to identify recently created objects versus long-lived objects. An object's generation is basically a counter that indicates how many times it has successfully avoided garbage collection. An object's generation counter starts at zero and can have a maximum value of two, after which the object's generation remains at this value regardless of how many times it is checked for collection.
You can put this to the test with a simple Visual Basic application. From the File menu, select either File ⇒ New ⇒ Project, or open the sample from the code download. Select a console application, provide a name and directory for your new project, and click OK. Within the Main module, add the following code snippet (code file: ProVB_Memory\Module1.vb):
Module Module1 Sub Main() Dim myObject As Object = New Object() Dim i As Integer For i = 0 To 3 Console.WriteLine(String.Format("Generation = {0}", _ GC.GetGeneration(myObject))) GC.Collect() GC.WaitForPendingFinalizers() Next i Console.Read() End Sub End Module
This code sends its output to the .NET console. For a Windows application, this console defaults to the Visual Studio Output window. When you run this code, it creates an instance of an object and then iterates through a loop four times. For each loop, it displays the current generation count of myObject and then calls the GC. The GC.WaitForPendingFinalizers method blocks execution until the garbage collection has been completed.
As shown in , each time the GC was run, the generation counter was incremented for myObject, up to a maximum of 2.
Each time the GC is run, the managed heap is compacted, and the reference to the end of the most recent memory allocation is updated. After compaction, objects of the same generation are grouped together. Generation-2 objects are grouped at the bottom of the managed heap, and generation-1 objects are grouped next. New generation-0 objects are placed on top of the existing allocations, so they are grouped together as well.
This is significant because recently allocated objects have a higher probability of having shorter lives. Because objects on the managed heap are ordered according to generations, the GC can opt to collect newer objects. Running the GC over a limited portion of the heap is quicker than running it over the entire managed heap.
It's also possible to invoke the GC with an overloaded version of the Collect method that accepts a generation number. The GC will then collect all objects no longer referenced by the application that belong to the specified (or younger) generation. The version of the Collect method that accepts no parameters collects objects that belong to all generations.
Another hidden GC optimization results from the fact that a reference to an object may implicitly go out of scope; therefore, it can be collected by the GC. It is difficult to illustrate how the optimization occurs only if there are no additional references to the object and the object does not have a finalizer. However, if an object is declared and used at the top of a module and not referenced again in a method, then in the release mode, the metadata will indicate that the variable is not referenced in the later portion of the code. Once the last reference to the object is made, its logical scope ends; and if the garbage collector runs, the memory for that object, which will no longer be referenced, can be reclaimed before it has gone out of its physical scope.
As shown in , while some of the classes in each namespace do inherit from each other, and while all of the classes eventually inherit from the generic Object, the classes in System.Text.RegularExpressions do not inherit from the classes in System.Text.
To emphasize the usefulness of namespaces, we can draw another good example from . If you make a reference to System.Drawing.Imaging.Encoder in your application, then you are making a reference to a completely different Encoder class than the namespace shown in —System.Text.Encoder. Being able to clearly identify classes that have the same name but very different functions, and disambiguate them, is yet another advantage of namespaces.
The System namespace, imported by default as part of every project created with Visual Studio, contains not only the default Object class, but also many other classes that are used as the basis for every .NET language.
What if a class you need isn't available in your project? The problem may be with the references in your project. For example, by default, the System.DirectoryServices namespace, used to get programmatic access to the Active Directory objects, is not part of your project's assembly. Using it requires adding a reference to the project assembly.
In fact, with all this talk about referencing, it is probably a good idea to look at an example of adding an additional namespace to a project. Before doing that, though, you should know a little bit about how a namespace is actually implemented.
Namespaces are implemented within .NET assemblies. The System namespace is implemented in an assembly called System.dll provided with Visual Studio. By referencing this assembly, the project is capable of referencing all the child namespaces of System that happen to be implemented in this assembly. Using the preceding table, the project can import and use the System.Text namespace, because its implementation is in the System.dll assembly. However, although it is listed, the project cannot import or use the System.Data namespace unless it references the assembly that implements this child of the System namespace, System.Data.dll.
You will now create a sample project so you can examine the role that namespaces play within it. Using Visual Studio 2012, create a new Visual Basic Console Application project; for the download this project was called ProVB_Namespaces.
The System.Collections library is not, by default, part of Visual Basic 2012 console applications. To gain access to the classes that this namespace provides, you need to add it to your project. You can do this by using the Add Reference dialog (available by right-clicking the Project Name node within the Visual Studio Solution Explorer). The Add Reference dialog has four tabs, each containing elements that can be referenced from your project:
The Add Reference dialog is shown in . The available .NET namespaces are listed by component name. This is the equivalent of the namespace name.
The list of default references automatically included varies depending on the type of project. By right-clicking on your project and going to the References tab you see the list shown in . If the project type were an ASP.NET Web service (not shown), then the list would include references to the System.Web and System.Web.Services namespaces.
In addition to making the namespaces available, references play a second important role in your project. One of the advantages of .NET is using services and components built on the common language runtime (CLR), which enables you to avoid DLL conflicts. The various problems that can occur related to DLL versioning, commonly referred to as DLL hell, involve two types of conflict.
The first situation occurs when you have a component that requires a minimum DLL version, and an older version of the same DLL causes your product to break. The alternative situation is when you require an older version of a DLL, and a new version is incompatible. In either case, the result is that a shared file, outside of your control, creates a system-wide dependency that affects your software. With .NET, it is possible, but not required, to indicate that a DLL should be shipped as part of your project to avoid an external dependency.
To indicate that a referenced component should be included locally, you can select the reference in the Solution Explorer and then examine the properties associated with that reference. One editable property is called Copy Local. You will see this property and its value in the Properties window within Visual Studio 2012. For those assemblies that are part of a Visual Studio 2012 installation, this value defaults to False, as shown in . However, for custom references, this property defaults to True to indicate that the referenced DLL should be included as part of the assembly. Changing this property to True changes the path associated with the assembly. Instead of using the path to the referenced file's location on the system, the project copies the referenced DLL into your application's runtime folder.
The benefit of this is that even when another version of the DLL is later placed on the system, your project's assembly will continue to call its local copy. However, this protection from a conflicting version comes at a price: Future updates to the namespace assembly to fix flaws will be in the system version, but not in the private version that is part of your project's assembly.
To resolve this, Microsoft's solution is to place new versions in directories based on their version information. If you examine the path information for all of the Visual Studio 2012 references, you will see that it includes a version number. As new versions of these DLLs are released, they are installed in a separate directory. This method allows for an escape from DLL hell by keeping new versions from overwriting old versions, and it enables old versions to be easily located for maintenance updates. Therefore, it is often best to leave alone the default behavior of Visual Studio 2012, which is set to copy only locally custom components, until your organization implements a directory structure with version information similar to that of Microsoft.
Visual Studio 2012 will not allow you to add a reference to your assembly if the targeted implementation includes a reference that is not also referenced in your assembly. The good news is that the compiler will help. The compiler will flag references to invalid namespaces with underlining, similar to the Microsoft Word spelling or grammar error underlines. When you click the underlined text, the compiler will either tell you which other assemblies need to be referenced in the project in order to use the class in question, or indicate that the namespace isn't available.
Every Visual Basic 2012 project includes the namespace Microsoft.VisualBasic. This namespace is part of the Visual Studio project templates for Visual Basic 2012 and is, in short, what makes Visual Basic 2012 different from C# or any other .NET language. The implicit inclusion of this namespace is the reason why you can call IsDBNull and other methods of Visual Basic 2012 directly. The only difference in the default namespaces included with Visual Basic 2012 and C# application projects is that the former use Microsoft.VisualBasic and the latter use Microsoft.CSharp.
shows an example of the namespaces that are imported automatically for a console application. Of course, to really make use of the classes and other objects in this list, you need more detailed information. In addition to resources such as Visual Studio 2010's help files, the best source of information is the Object Browser, available directly in the Visual Studio 2012 IDE. You can find it by selecting View ⇒ Object Browser if you are using Visual Studio 2012, 2010, 2005, or 2003. The Visual Studio 2012 Object Browser is shown in .
The Object Browser displays each of the referenced assemblies and enables you to drill down into the various namespaces. illustrates how the System.dll implements a number of namespaces, including some that are part of the System namespace. By drilling down into a namespace, you can see some of the classes available. By further selecting a class, the browser shows not only the methods and properties associated with the selected class, but also a brief outline of what that class does.
Using the Object Browser is an excellent way to gain insight into which classes and interfaces are available via the different assemblies included in your project, and how they work. Clearly, the ability to actually see which classes are available and know how to use them is fundamental to being able to work efficiently. Working effectively in the .NET CLR environment requires finding the right class for the task.
Not all namespaces should be imported at the global level. Although you have looked at the namespaces included at this level, it is much better to import namespaces only in the module where they will be used. As with variables used in a project, it is possible to define a namespace at the module level. The advantage of this is similar to using local variables in that it helps to prevent different namespaces from interfering with each other. As this section shows, it is possible for two different namespaces to contain classes or even child namespaces with the same name.
The development environment and compiler need a way to prioritize the order in which namespaces should be checked when a class is referenced. It is always possible to unequivocally specify a class by stating its complete namespace path. This is referred to as fully qualifying your declaration. The following example fully qualifies a StringBuilder object:
Dim sb = New System.Text.StringBuilder
However, if every reference to every class needed its full namespace declaration, then Visual Basic 2010 and every other .NET language would be very difficult to program in. After all, who wants to type System.Collections.ArrayList each time an instance of the ArrayList class is wanted? If you review the global references, you will recall we added a reference to the System.Collections namespace. Thus, you can just type ArrayList whenever you need an instance of this class, as the reference to the larger System.Collections namespace has already been made by the application.
In theory, another way to reference the StringBuilder class is to use Text.StringBuilder, but with all namespaces imported globally, there is a problem with this, caused by what is known as namespace crowding. Because there is a second namespace, System.Drawing, that has a child called Text, the compiler does not have a clear location for the Text namespace and, therefore, cannot resolve the StringBuilder class. The solution to this problem is to ensure that only a single version of the Text child namespace is found locally. That way, the compiler will use this namespace regardless of the global availability of the System.Drawing.Text namespace.
Imports statements specify to the compiler those namespaces that the code will use:
Imports Microsoft.Win32 Imports System Imports SysText = System.Text
Once they are imported into the file, you are not required to fully qualify your object declarations in your code. For instance, if you imported the System.Data.SqlClient namespace into your file, then you would be able to create a SqlConnection object in the following manner:
Dim conn As New SqlConnection
Each of the preceding Imports statements illustrates a different facet of importing namespaces. The first namespace, Microsoft.Win32, is not imported at the global level. Looking at the reference list, you may not see the Microsoft assembly referenced directly. However, opening the Object Browser reveals that this namespace is actually included as part of the System.dll.
As noted earlier, the StringBuilder references become ambiguous because both System.Text and System.Drawing.Text are valid namespaces at the global level. As a result, the compiler has no way to determine which Text child namespace is being referenced. Without any clear indication, the compiler flags Text.StringBuilder declarations in the command handler. However, using the Imports System declaration in the module tells the compiler that before checking namespaces imported at the global level, it should attempt to match incomplete references at the module level. Because the System namespace is declared at this level, if System.Drawing is not, then there is no ambiguity regarding to which child namespace Text.StringBuilder belongs.
This sequence demonstrates how the compiler looks at each possible declaration:
While the preceding logical progression of moving from a full declaration through module-level to global-level imports resolves the majority of issues, it does not handle all possibilities. Specifically, if you import System.Drawing at the module level, the namespace collision would return. This is where the third Imports statement becomes important—this Imports statement uses an alias.
Aliasing has two benefits in .NET. First, aliasing enables a long namespace such as System.EnterpriseServices to be replaced with a shorthand name such as COMPlus. Second, it adds a way to prevent ambiguity among child namespaces at the module level.
As noted earlier, the System and System.Drawing namespaces both contain a child namespace of Text. Because you will be using a number of classes from the System.Drawing namespace, it follows that this namespace should be imported into the form's module. However, were this namespace imported along with the System namespace, the compiler would again find references to the Text child namespace ambiguous. By aliasing the System.Drawing namespace to SysDraw, the compiler knows that it should check the System.Drawing namespace only when a declaration begins with that alias. The result is that although multiple namespaces with the same child namespace are now available at the module level, the compiler knows that one (or more) of them should be checked at this level only when they are explicitly referenced.
Aliasing as defined here is done in the following fashion:
Imports SysText = System.Text
Making a reference to a namespace in ASP.NET is quite similar to working with Windows applications, but you have to take some simple, additional steps. From your ASP.NET solution, first make a reference to the assemblies from the References folder, just as you do with Windows Forms. Once there, import these namespaces at the top of the page file in order to avoid having to fully qualify the reference every time on that particular page.
For example, instead of using System.Collections.Generic for each instance, use the < %# Import % > page directive at the top of the ASP.NET page (if the page is constructed using the inline coding style) or use the Imports keyword at the top of the ASP.NET page's code-behind file (just as you would with Windows Forms applications). The following example shows how to perform this task when using inline coding for ASP.NET pages:
<%# Import Namespace="System.Collections.Generic" %>
Now that this reference is in place on the page, you can access everything this namespace contains without having to fully qualify the object you are accessing. Note that the Import keyword in the inline example is not missing an “s” at the end. When importing in this manner, it is Import (without the “s”) instead of Imports—as it is in the ASP.NET code-behind model and Windows Forms.
In ASP.NET 1.0/1.1, if you used a particular namespace on each page of your application, you needed the Import statement on each and every page where that namespace was needed. ASP.NET 3.5 introduced the ability to use the web.config file to make a global reference so that you don't need to make further references on the pages themselves, as shown in the following example:
<pages> <namespaces> <add namespace="System.Drawing" /> <add namespace="Wrox.Books" /> </namespaces> </pages>
In this example, using the <namespaces> element in the web.config file, references are made to the System.Drawing namespace and the Wrox.Books namespace. Because these references are now contained within the web.config file, there is no need to again reference them on any of the ASP.NET pages contained within this solution.
The next step is optional, but, depending on whether you want to create a class at the top level or at a child level, you can add a Namespace command to your code. There is a trick to being able to create top-level namespaces or multiple namespaces within the modules that make up an assembly. Instead of replacing the default namespace with another name, you can delete the default namespace and define the namespaces only in the modules, using the Namespace command.
The Namespace command is accompanied by an End Namespace command. This End Namespace command must be placed after the End Class tag for any classes that will be part of the namespace. The following code demonstrates the structure used to create a MyMetaNamespace namespace, which contains a single class:
Namespace MyMetaNamespace Class MyClass1 ' Code End Class End Namespace
You can then utilize the MyClass1 object simply by referencing its namespace, MyMetaNamespace.MyClass1. It is also possible to have multiple namespaces in a single file, as shown here:
Namespace MyMetaNamespace1 Class MyClass1 ' Code End Class End Namespace Namespace MyMetaNamespace2 Class MyClass2 ' Code End Class End Namespace
Using this kind of structure, if you want to utilize MyClass1, then you access it through the namespace MyMetaNamespace.MyClass1. This does not give you access to MyMetaNamespace2 and the objects that it offers; instead, you have to make a separate reference to MyMetaNamespace2.MyClass2. Note typically it is best practice to place each class into a separate file; similarly, having multiple namespaces in a single file is not considered best practice.
The Namespace command can also be nested. Using nested Namespace commands is how child namespaces are defined. The same rules apply — each Namespace must be paired with an End Namespace and must fully encompass all of the classes that are part of that namespace. In the following example, the MyMetaNamespace has a child namespace called MyMetaNamespace.MyChildNamespace:
Namespace MyMetaNamespace Class MyClass1 ' Code End Class Namespace MyChildNamespace Class MyClass2 ' Code End Class End Namespace End Namespace
This is another point to be aware of when you make references to other namespaces within your own custom namespaces. Consider the following example:
Imports System Imports System.Data Imports System.Data.SqlClient Imports System.IO Namespace MyMetaNamespace1 Class MyClass1 ' Code End Class End Namespace Namespace MyMetaNamespace2 Class MyClass2 ' Code End Class End Namespace
In this example, a number of different namespaces are referenced in the file. The four namespaces referenced at the top of the code listing—the System, System.Data, and System.Data.SqlClient namespace references—are available to every namespace developed in the file. This is because these three references are sitting outside of any particular namespace declarations. However, the same is not true for the System.IO namespace reference. Because this reference is made within the MyMetaNamespace2 namespace, it is unavailable to any other namespace in the file.
Sometimes when you are working with custom namespaces, you might find that you have locked yourself out of accessing a particular branch of a namespace, purely due to naming conflicts. Visual Basic includes the Global keyword, which can be used as the outermost root class available in the .NET Framework class library. shows a diagram of how the class structure looks with the Global keyword.
This means that you can make specifications such as:
Global.System.String
or
Global.Wrox.System.Titles
Property/Method | Description |
ApplicationContext | Returns contextual information about the thread of the Windows Forms application. |
ChangeCulture | A method that enables you to change the culture of the current application thread. |
ChangeUICulture | A method that enables you to change the culture that is being used by the Resource Manager. |
Culture | Returns the current culture being used by the current thread. |
Deployment | Returns an instance of the ApplicationDeployment object, which allows for programmatic access to the application's ClickOnce features. |
GetEnvironmentVariable | A method that enables you to access the value of an environment variable. |
Info | Provides quick access to the assembly of Windows Forms. You can retrieve assembly information such as version number, name, title, copyright information, and more. |
IsNetworkDeployed | Returns a Boolean value that indicates whether the application was distributed via the network using the ClickOnce feature. If True, then the application was deployed using ClickOnce—otherwise False. |
Log | Enables you to write to your application's Event Log listeners. |
MinimumSplashScreenDisplayTime | Enables you to set the time for the splash screen. |
OpenForms | Returns a FormCollection object, which allows access to the properties of the forms currently open. |
SaveMySettingsOnExit | Provides the capability to save the user's settings upon exiting the application. This method works only for Windows Forms and console applications. |
SplashScreen | Enables you to programmatically assign the splash screen for the application. |
UICulture | Returns the current culture being used by the Resource Manager. |
Much can be accomplished using the My.Application namespace. For an example of its use, you will focus on the Info property. This property provides access to the information stored in the application's AssemblyInfo.vb file, as well as other details about the class file. In one of your applications, you can create a message box that is displayed using the following code (code file: ProVB_Namespaces\Module1.vb):
System.Windows.Forms.MessageBox.Show("Company Name: " & My.Application.Info.CompanyName & vbCrLf & "Description: " & My.Application.Info.Description & vbCrLf & "Directory Path: " & My.Application.Info.DirectoryPath & vbCrLf & "Copyright: " & My.Application.Info.Copyright & vbCrLf & "Trademark: " & My.Application.Info.Trademark & vbCrLf & "Name: " & My.Application.Info.AssemblyName & vbCrLf & "Product Name: " & My.Application.Info.ProductName & vbCrLf & "Title: " & My.Application.Info.Title & vbCrLf & "Version: " & My.Application.Info.Version.ToString())
From this example, it is clear that you can get at quite a bit of information concerning the assembly of the running application. Running this code produces a message box similar to the one shown in .
Another interesting property to look at from the My.Application namespace is the Log property. This property enables you to work with the log files for your application. For instance, you can easily write to the system's Application Event Log by first changing the application's app.config file to include the following (code file: ProVB_Namespaces\app.config.txt):
<?xml version="1.0" encoding="utf-8" ?> <configuration> <startup> <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" /> </startup> <system.diagnostics> <sources> <source name="DefaultSource" switchName="DefaultSwitch"> <listeners> <add name="EventLog"/> </listeners> </source> </sources> <switches> <add name="DefaultSwitch" value="Information" /> </switches> <sharedListeners> <add name="EventLog" type="System.Diagnostics.EventLogTraceListener" initializeData="ProVBEventWriter" /> </sharedListeners> </system.diagnostics> </configuration>
Once the configuration file is in place, you can record entries to the Application Event Log. Note that writing to the Application Event Log is a privileged action, and you need to have started Visual Studio 2012 as an administrator on your machine. However, the code shown in the following simple example can leverage the configuration settings previously shown to leave a message within the event log (code file: ProVB_Namespaces\Module1.vb):
My.Application.Log.WriteEntry("Entered Form1_Load", _ TraceEventType.Information, 1)
You could also just as easily use the WriteExceptionEntry method in addition to the WriteEntry method. After running this application and looking in the Event Viewer, you will see the event shown in .
The previous example shows how to write to the Application Event Log when working with the objects that write to the event logs. In addition to the Application Event Log, there is also a Security Event Log and a System Event Log. Note that when using these objects, it is impossible to write to the Security Event Log, and it is only possible to write to the System Event Log if the application does it under either the Local System or the Administrator accounts.
In addition to writing to the Application Event Log, you can just as easily write to a text file. As with writing to the Application Event Log, writing to a text file also means that you need to make changes to the app.config file (code file: app.config to support writing to filelog):
<?xml version="1.0" encoding="utf-8" ?> <configuration> <startup> <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" /> </startup> <system.diagnostics> <sources> <source name="DefaultSource" switchName="DefaultSwitch"> <listeners> <add name="EventLog"/> <add name="FileLog" /> </listeners> </source> </sources> <switches> <add name="DefaultSwitch" value="Information" /> </switches> <sharedListeners> <add name="EventLog" type="System.Diagnostics.EventLogTraceListener" initializeData="ProVBEventWriter" /> <add name="FileLog" type="Microsoft.VisualBasic.Logging.FileLogTraceListener, Microsoft.VisualBasic, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL" initializeData="FileLogWriter"/> </sharedListeners> </system.diagnostics> </configuration>
Now with this app.config file in place, you simply need to run the same WriteEntry method as before. This time, however, in addition to writing to the Application Event Log, the information is also written to a new text file. You can find the text file at C:\Users\[username]\AppData\Roaming\[AssemblyCompany]\[AssemblyProduct]\[Version]. For instance, in my example, the log file was found at C:\Users\WSheldon\AppData\Roaming\Microsoft\ProVB_Namespaces\1.0.0.0\. In the .log file found, you will see a line such as the following:
DefaultSource Information 1 Entered Form1_Load
By default, it is separated by tabs, but you can change the delimiter yourself by adding a delimiter attribute to the FileLog section in the app.config file:
<add name="FileLog" type="Microsoft.VisualBasic.Logging.FileLogTraceListener, Microsoft.VisualBasic, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL" initializeData="FileLogWriter" delimiter=";" />
In addition to writing to Event Logs and text files, you can also write to XML files, console applications, and more.
The My.Computer namespace can be used to work with the parameters and details of the computer in which the application is running. details the objects contained in this namespace.
Property | Description |
Audio | This object enables you to work with audio files from your application. This includes starting, stopping, and looping audio files. |
Clipboard | This object enables you to read and write to the clipboard. |
Clock | This enables access to the system clock to get at GMT and the local time of the computer running the application. You can also get at the tick count, which is the number of milliseconds that have elapsed since the computer was started. |
FileSystem | This object provides a large collection of properties and methods that enable programmatic access to drives, folders, and files. This includes the ability to read, write, and delete items in the file system. |
Info | This provides access to the computer's details, such as amount of memory, the operating system type, which assemblies are loaded, and the name of the computer itself. |
Keyboard | This object provides information about which keyboard keys are pressed by the end user. Also included is a single method, SendKeys, which enables you to send the pressed keys to the active form. |
Mouse | This provides a handful of properties that enable detection of the type of mouse installed, including details such as whether the left and right mouse buttons have been swapped, whether a mouse wheel exists, and how much to scroll when the user uses the wheel. |
Name | This is a read-only property that provides access to the name of the computer. |
Network | This object provides a single property and some methods that enable you to interact with the network to which the computer running the application is connected. With this object, you can use the IsAvailable property to first verify that the computer is connected to a network. If so, then the Network object enables you to upload or download files, and ping the network. |
Ports | This object can provide notification when ports are available, as well as allow access to the ports. |
Registry | This object provides programmatic access to the registry and the registry settings. Using the Registry object, you can determine whether keys exist, determine values, change values, and delete keys. |
Screen | This provides the capability to work with one or more screens that may be attached to the computer. |
The My.Resources namespace is a very easy way to get at the resources stored in your application. If you open the MyResources.resx file from the My Project folder in your solution, you can easily create as many resources as you wish. For example, you could create a single String resource titled MyResourceString and give it a value of St. Louis Rams.
To access the resources that you create, use the simple reference shown here:
My.Resources.MyResourceString.ToString()
Using IntelliSense, all of your created resources will appear after you type the period after the MyResources string.
The My.User namespace enables you to work with the IPrincipal interface. You can use the My.User namespace to determine whether the user is authenticated or not, the user's name, and more. For instance, if you have a login form in your application, you could allow access to a particular form with code similar to the following:
If (Not My.User.IsInRole("Administrators")) Then ' Code here End If You can also just as easily get the user's name with the following: My.User.Name In addition, you can check whether the user is authenticated: If My.User.IsAuthenticated Then ' Code here End If
When not using the My.WebServices namespace, you access your Web services references in a lengthier manner. The first step in either case is to make a Web reference to some remote XML Web Service in your solution. These references will then appear in the Web References folder in the Solution Explorer in Visual Studio 2012. Before the introduction of the My namespace, you would have accessed the values that the Web reference exposed in the following manner:
Dim ws As New RubiosMenu.GetMenuDetails Label1.Text = ws.GetLatestPrice.ToString()
This works, but now with the My namespace, you can use the following construct:
Label1.Text = My.WebServices.GetMenuDetails.GetLatestPrice.ToString()
This chapter introduced the CLR. You first looked at its memory management features, including how .NET uses namespaces to structure the classes available within the Framework. The chapter reviewed how the .NET Framework is handling multiple profiles and touched on the Visual Basic specific features of the My namespace. Chapter highlights include the following:
This chapter also examined the value of a common runtime and type system that can be targeted by multiple languages. This chapter illustrated how namespaces play an important role in the .NET Framework and your software development. They enable Visual Studio to manage and identify the appropriate libraries for use in different versions of .NET or when targeting different .NET platforms. Anyone who has ever worked on a large project has experienced situations in which a fix to a component was delayed because of the potential impact on other components in the same project. The CLR has the ability to version and support multiple versions of the same project. Regardless of the logical separation of components in the same project, developers who take part in the development process worry about collisions. With separate implementations for related components, it is not only possible to alleviate this concern, but also easier than ever before for a team of developers to work on different parts of the same project.
Having looked at the primary development tool in Chapter 1 and now reviewing the runtime environment, Chapter 3 will begin the process of using Visual Basic's core fundamental structure — the class.