|
Chapter 1: Introduction to the .NET Framework
Chapter 1 Introduction to the .NET FrameworkAbout This ChapterThis chapter discusses the Microsoft .NET Framework and the common language runtime. It also provides an introduction to the syntax of class, structure, and method declaration. Before You Begin There are no prerequisites to completing the lessons in this chapter.
Lesson 1: The .NET Framework and the Common Language RuntimeThe Microsoft .NET Framework is an integrated and managed environment for the development and execution of your code. This lesson is an introduction to the .NET Framework, the philosophy behind it, and how it works.
Estimated lesson time: 20 minutes Overview of the .NET Framework The .NET Framework is a managed type-safe environment for application development and execution. The .NET Framework manages all aspects of your program's execution. It allocates memory for the storage of data and instructions, grants or denies the appropriate permissions to your application, initiates and manages application execution, and manages the reallocation of memory from resources that are no longer needed. The .NET Framework consists of two main components: the common language runtime and the .NET Framework class library. The common language runtime can be thought of as the environment that manages code execution. It provides core services, such as code compilation, memory allocation, thread management, and garbage collection. Through the common type system (CTS), it enforces strict type-safety and ensures that code is executed in a safe environment by also enforcing code access security. The .NET Framework class library provides a collection of useful and reusable types that are designed to integrate with the common language runtime. The types provided by the .NET Framework are object-oriented and fully extensible, and they allow you to seamlessly integrate your applications with the .NET Framework. The .NET base class library is discussed further in Lesson 2 of this chapter. Languages and the .NET Framework The .NET Framework is designed for cross-language compatibility, which means, simply, that .NET components can interact with each other no matter what supported language they were written in originally. So, an application written in Microsoft Visual Basic .NET might reference a dynamic-link library (DLL) file written in Microsoft Visual C#, which in turn might access a resource written in managed Microsoft Visual C++ or any other .NET language. This language interoperability extends to full object-oriented inheritance. A Visual Basic .NET class might be derived from a C# class, for example, or vice versa. This level of cross-language compatibility is possible because of the common language runtime. When a .NET application is compiled, it is converted from the language in which it was written (Visual Basic .NET, C#, or any other .NET-compliant language) to Microsoft Intermediate Language (MSIL or IL). MSIL is a low-level language that the common language runtime can read and understand. Because all .NET executables and DLLs exist as MSIL, they can freely interoperate. The Common Language Specification (CLS) defines the minimum standards to which .NET language compilers must conform. Thus, the CLS ensures that any source code successfully compiled by a .NET compiler can interoperate with the .NET Framework. The CTS ensures type compatibility between .NET components. Because .NET applications are converted to IL prior to deployment and execution, all primitive data types are represented as .NET types. Thus, a Visual Basic Integer and a C# int are both represented in IL code as a System.Int32. Because both languages use a common type system, it is possible to transfer data between components and avoid time-consuming conversions or hard-to-find errors. Visual Studio .NET ships with languages such as Visual Basic .NET, Visual C#, and Visual C++ with managed extensions, as well as the JScript scripting language. You can also write managed code for the .NET Framework in other languages. Third-party tools and compilers exist for Fortran, Cobol, Perl, and a host of other languages. All of these languages share the same cross-language compatibility and inheritability. Thus, you can write code for the .NET Framework in the language of your choice, and it will be able to interact with code written for the .NET Framework in any other language.
The Structure of a .NET Application To understand how the common language runtime manages code execution, you must examine the structure of a .NET application. The primary unit of a .NET application is the assembly. An assembly is a self-describing collection of code, resources, and metadata. The assembly manifest contains information about what is contained within the assembly. The assembly manifest provides:
Each assembly has one and only one assembly manifest, and it contains all the description information for the assembly. However, the assembly manifest can be contained in its own file or within one of the assembly's modules. An assembly contains one or more modules. A module contains the code that makes up your application or library, and it contains metadata that describes that code. When you compile a project into an assembly, your code is converted from high-level code to IL. Because all managed code is first converted to IL code, applications written in different languages can easily interact. For example, one developer might write an application in Visual C# that accesses a DLL in Visual Basic .NET. Both resources will be converted to IL modules before being executed, thus avoiding any language-incompatibility issues. Each module also contains a number of types. Types are templates that describe a set of data encapsulation and functionality. There are two kinds of types: reference types (classes) and value types (structures). These types are discussed in greater detail in Lesson 2 of this chapter. Each type is described to the common language runtime in the assembly manifest. A type can contain fields, properties, and methods, each of which should be related to a common functionality. For example, you might have a class that represents a bank account. It contains fields, properties, and methods related to the functions needed to implement a bank account. A field represents storage of a particular type of data. One field might store the name of an account holder, for example. Properties are similar to fields, but properties usually provide some kind of validation when data is set or retrieved. You might have a property that represents an account balance. When an attempt is made to change the value, the property can check to see if the attempted change is greater than a predetermined limit. If the value is greater than the limit, the property does not allow the change. Methods represent behavior, such as actions taken on data stored within the class or changes to the user interface. Continuing with the bank account example, you might have a Transfer method that transfers a balance from a checking account to a savings account, or an Alert method that warns users when their balances fall below a predetermined level. Compilation and Execution of a .NET Application When you compile a .NET application, it is not compiled to binary machine code; rather, it is converted to IL. This is the form that your deployed application takesone or more assemblies consisting of executable files and DLL files in IL form. At least one of these assemblies will contain an executable file that has been designated as the entry point for the application. When execution of your program begins, the first assembly is loaded into memory. At this point, the common language runtime examines the assembly manifest and determines the requirements to run the program. It examines security permissions requested by the assembly and compares them with the system's security policy. If the system's security policy does not allow the requested permissions, the application will not run. If the application passes the system's security policy, the common language runtime executes the code. It creates a process for the application to run in and begins application execution. When execution starts, the first bit of code that needs to be executed is loaded into memory and compiled into native binary code from IL by the common language runtime's Just-In-Time (JIT) compiler. Once compiled, the code is executed and stored in memory as native code. Thus, each portion of code is compiled only once when an application executes. Whenever program execution branches to code that has not yet run, the JIT compiler compiles it ahead of execution and stores it in memory as binary code. This way, application performance is maximized because only the parts of a program that are executed are compiled. Lesson Summary
Lesson 2: The .NET Base Class LibraryThe .NET base class library is a collection of object-oriented types and interfaces that provide object models and services for many of the complex programming tasks you will face. Most of the types presented by the .NET base class library are fully extensible, allowing you to build types that incorporate your own functionality into your managed code. This lesson introduces some of the .NET base class library namespaces and describes how to reference the library and use its types and methods.
Estimated lesson time: 30 minutes The .NET Framework base class library contains the base classes that provide many of the services and objects you need when writing your applications. The class library is organized into namespaces. A namespace is a logical grouping of types that perform related functions. For example, the System.Windows.Forms namespace contains all the types that make up Windows forms and the controls used in those forms. Namespaces are logical groupings of related classes. The namespaces in the .NET base class library are organized hierarchically. The root of the .NET Framework is the System namespace. Other namespaces can be accessed with the period operator. A typical namespace construction appears as follows:
System The first example refers to the System namespace. The second refers to the System.Data namespace. The third example refers to the System.Data.SQLClient namespace. Table 1.1 introduces some of the more commonly used .NET base class namespaces. Table 1.1 Representative .NET Namespaces
The namespace names are self-descriptive by design. Straightforward names make the .NET Framework easy to use and allow you to rapidly familiarize yourself with its contents. Reference Types and Value Types Types in the .NET Framework come in two varieties: value types and reference types. The primary difference between value types and reference types has to do with the way variable data is accessed. To understand this difference, a little background on memory dynamics is required. Application data memory is divided into two primary components, the stack and the heap. The stack is an area of memory reserved by the application to run the program. The stack is analogous to a stack of dinner plates. Plates are placed on the stack one on top of another. When a plate is removed from the stack, it is always the last one to have been placed on top that is removed first. So it is with program variables. When a function is called, all the variables used by the function are pushed onto the stack. If that function calls additional functions, it pushes additional variables onto the stack. When the most recently called function terminates, all of its variables go out of scope (meaning that they are no longer available to the application) and are popped off the stack. Memory consumed by those variables is then freed up, and program execution continues. The heap, on the other hand, is a separate area of memory reserved for the creation of reusable objects. The common language runtime manages allocation of heap memory for objects and controls the reclamation of memory from unused objects through garbage collection.
All the data associated with a value type is allocated on the stack. When a variable of a value type goes out of scope, it is destroyed and its memory is reclaimed. A variable of a reference type, on the other hand, exists in two memory locations. The actual object data is allocated on the heap. A variable containing a pointer to that object is allocated on the stack. When that variable is called by a function, it returns the memory address for the object to which it refers. When that variable goes out of scope, the object reference is destroyed but the object itself is not. If any other references to that object exist, the object remains intact. If the object is left without any references, it is subject to garbage collection. (See Lesson 6 of this chapter.) Examples of value types include primitives, such as Integer (int), Boolean (bool), Char (char), and so on, as well as user-defined types such as Structure (struct) and Enumeration (enum). Classes represent the majority of reference types. Other reference types include the interface, delegate, and array types. Classes and structures are discussed in Lesson 3 of this chapter, and other reference and value types are discussed in Chapter 3.
Using .NET Framework Types in Your Application When you begin writing an application, you automatically begin with a reference to the .NET Framework base class library. You reference it so that your application is aware of the base class library and is able to create instances of the types represented by it. Value Types In Visual Basic .NET, you use the Dim statement to create a variable that represents a value type. In C#, you create a variable by declaring its type and then the variable name. The following code is an example: Visual Basic .NET
Dim myInteger As Integer Visual C#
int myInteger; This line tells the runtime to allocate the appropriate amount of memory to hold an integer variable. Although this line creates the variable, it does not assign a value to it. You can assign a value using the assignment operator, as follows: Visual Basic .NET
myInteger = 42 Visual C#
myInteger = 42; You can also choose to assign a value to a variable upon creation, as shown in this example: Visual Basic .NET
Dim myInteger As Integer = 42 Visual C#
int myInteger = 42;
Reference Types Creating an instance of a type is a two-step process. The first step is to declare the variable as that type, which allocates the appropriate amount of memory for that variable but does not actually create the object. The following syntax declares an object: Visual Basic .NET
Dim myForm As System.Windows.Forms.Form Visual C#
System.Windows.Forms.Form myForm; This line tells the runtime to set aside enough memory to hold a Form variable and assigns it the name myForm, but it does not actually create the Form object in memory. The second step, called instantiation, actually creates the object. An example of instantiation follows: Visual Basic .NET
myForm = New System.Windows.Forms.Form() Visual C#
myForm = new System.Windows.Forms.Form(); This line makes a call to the constructor method of the type System.Windows.Forms.Form by way of the New (new) keyword. The constructor is a special method that is invoked only at the beginning of an object's lifetime. It contains any code that must be executed for the object to work (assigning values to properties, for example). If any parameters were required by the constructor, they would be contained within the parentheses at the end of the line. The following example shows declaration and instantiation of a hypothetical Widget class that requires a string as a parameter in the constructor. For further discussion of the constructor, see Lesson 4 in this chapter. Visual Basic .NET
Dim myWidget As Widget Visual C#
Widget myWidget; If desired, you can also combine both declaration and instantiation into a single statement. By declaring and instantiating an object in the same line, you reserve the memory for the object and immediately create the object that resides in that memory. Although there was a significant performance penalty for this shortcut in previous versions of Visual Basic, Visual Basic .NET and Visual C# are optimized to allow this behavior without any performance loss. The following example shows the one-step declaration and instantiation of a new Form: Visual Basic .NET
Dim myForm As New System.Windows.Forms.Form() Visual C#
System.Windows.Forms.Form myForm = new Both value types and reference types must be initialized before use. For class and structure fields in Visual Basic .NET, types are initialized with default values on declaration. Numeric value types (such as integer) and floating-point types are assigned zero; Boolean variables are assigned False; and reference types are assigned to a null reference. In C#, variables of a reference type have a default value of null. It is recommended that you do not rely on the default value. These variables should not be used until they have been initialized. Using Value Type and Reference Type Variables A variable that represents a value type contains all the data represented by that type. A variable that represents a reference type contains a reference to a particular object. This distinction is important. Consider the following example: Visual Basic .NET
Dim x, y As integer Visual C#
int x, y; In this example, two integer variables named x and y are created. X is assigned a value of 15, and then y is assigned the value of x. Next the value of x is changed to 30, and the question is posed: what is the value of y? The answer to this question might seem obvious, and it is y = 15 because x and y are two separate variables and have no effect on each other when changed. When the line y = x is encountered, the value of x is copied to the value of y, and there is no further connection between the two variables. This situation changes, however, in the case of reference types. Let's reconsider the previous example using a reference type (Form) instead of a value type. Visual Basic .NET
Dim x, y As System.Windows.Forms.Form Visual C#
System.Windows.Forms.Form x,y; What value does y.Text return? This time, the answer is less obvious. Because System.Windows.Forms.Form is a reference type, the variable x does not actually contain a Form; rather, it points to an instance of a Form. When the line y = x is encountered, the runtime copies the reference from variable x to y. Thus, the variables x and y now point to the same instance of Form. Because these two variables refer to the same instance of the object, they will return the same values for properties of that object. Thus, y.Text returns "This is Form 2". The Imports and Using Statements Up to this point of the chapter, if you wanted to access a type in the .NET Framework base class library, you had to use the full name of the type, including every namespace to which it belonged. For example:
System.Windows.Forms.Form This is called the fully-qualified name, meaning it refers both to the class and to the namespace in which it can be found. You can make your development environment "aware" of various namespaces by using the Imports (Visual Basic .NET) or using (Visual C#) statement. This technique allows you to refer to a type using only its generic name and to omit the qualifying namespaces. Thus, you could refer to System.Windows.Forms.Form as simply Form. In Visual Basic .NET, the Imports statement must be placed at the top of the code window, preceding any other statement (except Option). In Visual C#, the using statement must occur before any other namespace element, such as a class or struct. This example demonstrates use of this statement: Visual Basic .NET
Imports System.Windows.Forms Visual C#
using System.Windows.Forms; When two types of the same name exist in more than one imported namespace, you must use the fully qualified name to avoid a naming conflict. Thus, if you are using MyNameSpaceOne and MyNameSpaceTwo, and each contains a Widget class, you would have to refer to MyNameSpaceOne.Widget or MyNameSpaceTwo.Widget to ensure the correct result. In C#, you can resolve namespace conflicts such as these by creating an alias. An alias allows you to choose one name to refer to another class. You create an alias using the using keyword, as shown below: Visual C#
using myAlias = MyNameSpaceTwo.Widget; After implementing an alias, you can use it in code to represent the aliased class. For example: Visual C#
// You can now refer to MyNameSpaceTwo as myAlias. The You cannot create aliases for types in this manner in Visual Basic .NET. Referencing External Libraries You might want to use class libraries not contained by the .NET Framework, such as libraries developed by third-party vendors or libraries you developed. To access these external libraries, you must create a reference. To create a reference to an external library
Lesson Summary
Lesson 3: Using Classes and StructuresYou have seen how the .NET Framework base class library provides a plethora of standard types to help you in the development of your applications. You can also create user-defined types that implement custom behaviors. Classes and structures represent the two principal user-defined types.
Estimated lesson time: 30 minutes Classes are templates for objects. They describe the kind and amount of data that an object will contain, but they do not represent any particular instance of an object. A real-world example of a class might be "Car"the abstract idea of what a car is. You know that a car has an engine, four wheels, a body color, an individual fuel efficiency, and a dozen other properties. Although the Car class would describe all these properties, as well as have descriptions of actions that the car might perform (roll forward, turn on windshield wipers, and so on), the class would not represent any particular car. Your car, on the other hand, is an object. It has a specific color, a specific fuel efficiency, a specific engine, and four specific wheels. A different car might have different values for each of these properties, but both would be recognizable as being an instance of the Car class. Members Classes describe the properties and behaviors of the objects they represent through members. Members are methods, fields, properties, and events that belong to a particular class. Fields and properties represent the data about an objectthe color of the car, its fuel efficiency, and whether it has an automatic or manual transmission, for example. A method represents something the object can do, such as move forward or turn on headlights. An event represents something interesting that happens to the object, such as overheating or crashing.
Creating Classes You create a new class by using the Class (Visual Basic .NET) or class (C#) keyword. For example: Visual Basic .NET
Public Class Widget Visual C#
public class Widget In this example, you use the Class (class) keyword to create a user-defined class. Widget is the name of the class, and the Public (public) keyword specifies the access level. Access levels are examined in greater detail in Lesson 5 of this chapter. Creating Structures Creating structures is very similar to creating classes. You use the Structure (Visual Basic .NET) or struct (C#) keyword. For example: Visual Basic .NET
Public Structure Vector Visual C#
public struct Vector Adding Members In Visual Basic .NET, a class comprises everything between the Class keyword and the End Class keyword. In C#, a class comprises everything within braces ({}). Structures are similar. Within the bounds of a class or a structure, you add the members. The following example demonstrates adding a member field to your Widget class: Visual Basic .NET
Public Class Widget Visual C#
public class Widget Your Widget class now contains a member variable named Spin. This variable has a Public (public) access level and can contain an Integer (int) value. Adding methods as members of your class or structure is discussed in Lesson 4 of this chapter. Nested Types Types can contain other types. Types within types are called nested types. Using classes as an example, a nested class usually represents an object that the parent class might need to create and manipulate, but which an external object would never need to create independently. An abstract example might be a Wheel class. A Wheel class might need to create and maintain a collection of Spoke objects internally, but outside users would probably never need to create a Spoke object independent of a wheel. A more realistic example might be an AccountManager class that controls all the interaction with Account objects. You might not want to allow Account objects to be created independently of the AccountManager class, so you would make Account a nested class inside AccountManager. This does not mean that outside objects can never instantiate objects based on nested classesthis depends on the access level of both the parent class and the nested class. See Lesson 5 of this chapter for more detail. An example of a nested class follows: Visual Basic .NET
Public Class Widget Visual C#
public class Widget Instantiating User-Defined Types You declare and instantiate a user-defined type the same way that you declare and instantiate a .NET Framework type. For both value types (structures) and reference types (classes), you need to declare the variable as a variable of that type and then create an instance of it with the New (new) keyword. Examples are as follows: Visual Basic .NET
Public Class Demo Visual C#
public class Demo Classes vs. Structures On the surface, classes and structures appear to be very similar. Both can contain members such as fields and methods, both require a constructor to create a new instance of themselves, and like all types in the .NET Framework, both inherit from Object. The key difference between classes and structures is that classes are reference types and structures are value types. On a low level, this means that the instance data for classes is allocated on the heap, whereas the instance data for structures is allocated on the stack. Access to the stack is designed to be light and fast, but storage of large amounts of data on the stack can impede overall application performance. In practical terms, that structures are best used for smaller, lightweight objects that contain relatively little instance data or for objects that do not persist for long. Classes are best used for larger objects that contain more instance data and are expected to exist in memory for extended periods. Lesson Summary
Lesson 4: Using MethodsMethods do the work of classes and structures. They calculate values, update data, receive input, and perform all the manipulations that make up the behavior of a type. In this lesson, you will learn how to create methods, use parameters, and create constructors and destructors for your class.
Estimated lesson time: 45 minutes Adding Methods You can add methods as members to your classes. Methods represent actions your class can take. Methods generally come in two varieties: those that return a value (functions in Visual Basic) and those that do not return a value (subs in Visual Basic). The following code shows an example of both kinds of methods: Visual Basic .NET
Public Sub MySub() Visual C# makes no distinction between methods that return a value and methods that do not. In either case, you must specify the return value type. If the method does not return a value, its return type is void. Here are examples of C# methods: Visual C#
public void myVoidMethod() Calling Methods A method does not execute until it is called. You can call a method by referencing its name along with any required parameters. For example: Visual Basic .NET
' This line calls the Rotate method, with two parameters Visual C#
// This line calls the Rotate method, with two parameters The Main method is a special case. It is called upon initiation of program execution. Destructors, another special case, are called by the runtime just prior to destruction of an object. Constructors, a third special case, are executed by an object during its initialization. These methods are discussed further later in this lesson. Method Variables Variables declared within methods are said to have method scope, which means that once the methods complete execution, they are destroyed and their memory reclaimed. They are said to have gone out of scope. Variables within smaller divisions of methods have even more limited scope. For example, variables declared within a For-Next (for) loop are accessible only within the loop. The following example demonstrates this because the variable Y has gone out of scope: Visual Basic .NET
Public Sub myMethod() Visual C#
public void myMethod() Visual Basic allows you to create method variables that are not destroyed after a method finishes execution. These variables, called static method variables, persist in memory and retain their values through multiple executions of a method. You declare a static variable with the Static keyword as follows: Visual Basic .NET
Public Sub myMethod() Although this variable persists in memory, it is still available only during execution of this method. You would use a Static variable for a method that needed to keep track of how many times it had been called.
Parameters A method can take one or more parameters. A parameter is an argument that is passed to the method by the method that calls it. Parameters are enclosed in parentheses after the method name in the method declaration, and types must be specified for parameters. Here is an example of a method with parameters: Visual Basic .NET
Public Sub DisplayName(ByVal name As String, ByVal age As Byte) Visual C#
public void DisplayName(string name, byte age) This method requires two parameters: a String parameter, which is given the local name name, and a Byte parameter, which is given the local name age. These variables have scope only for the duration of the method, and they cannot be used after the method returns. For a further discussion of scope, see Lesson 5 of this chapter. Parameters can be passed in two ways, by value or by reference. In the .NET Framework, parameters are passed by value by default. By value means that whenever a parameter is supplied, a copy of the data contained in the variable is made and passed to the method. Any changes made in the value passed to the method are not reflected in the original variable. Although it is the default setting, you can explicitly indicate that a variable be passed by value in Visual Basic with the ByVal keyword. When parameters are passed by reference, on the other hand, a reference to the memory location where the variable resides is supplied instead of an actual value. Thus, every time the method performs a manipulation on that variable, the changes are reflected in the actual object. To pass a parameter by reference in Visual Basic .NET, you use the keyword ByRef. In Visual C#, the keyword ref is used. The following example demonstrates passing parameters by value or by reference: Visual Basic .NET
Public Sub Demo1() Visual C#
public void Demo1() In this example, two variables named x and y are created and assigned values. The variables x and y are then passed to the second method. X is passed by value, y is passed by reference, and both are represented in the second method as the variables p1 and p2. Because p1 is passed by value, it represents a copy of the data stored in x, and the manipulations performed on it are for naught. Once the method ends, the variable goes out of scope and its memory is reclaimed. The parameter p2, on the other hand, does not contain a value at all; rather, it contains a reference to the actual data stored in the variable y. Thus, when the line p2 = p2 + p1 is reached, the value stored at the memory location represented by p2 is changed. Therefore, when the final line of the Demo1 method is reached, the value of x will be unchanged at 15, but the value of y will have changed and will be equal to 55. Note that if your parameter is a reference type, it makes no difference if the parameter is passed by value or by referencethe behavior will be the same. In both cases, any manipulations done on the parameter will be reflected in the object passed as a parameter. Output Parameters In Visual C#, you can also use output parameters. This feature is not available in Visual Basic .NET. An output parameter is a parameter that is passed from a called method to the method that called itthat is, in the reverse direction. Output parameters are useful if you want a method to return more than a single value. An output parameter is specified by using the out keyword. Output parameters are always passed by reference and do not need to be initialized before use. The following example demonstrates output parameters: Visual C#
public void aWord (out string Word) Here the ShowWord method calls the aWord method with the output parameter Word. The aWord method assigns a value to the output parameter Word, thereby assigning a value to the Word variable. Optional Parameters In Visual Basic .NET, you are able to specify optional parameters for your methods. This feature is not available in Visual C#. You specify a parameter as optional using the Optional keyword. Optional parameters must be the last parameters in a method declaration, and you must supply default values for optional parameters. The following example demonstrates the use of optional parameters: Visual Basic .NET
Public Sub Cook(ByVal time As Integer, Optional ByVal temp As _ Constructors and Destructors The constructor is the first method that is run when an instance of a type is created. In Visual Basic, the constructor is always Sub New. In Visual C#, it is a method with the same name as the class. You use a constructor to initialize class and structure data before use. Constructors can never return a value and can be overridden to provide custom initialization functionality. Chapter 4 discusses how to override methods. A constructor can also contain calls to other methods. An example of a constructor follows: Visual Basic .NET
Public Class aClass Visual C#
public class aClass Similarly, a destructor is the last method run by a class. A destructor (known as a finalizer in Visual Basic) contains code to "clean up" when a class is destroyed. This cleanup might include decrementing counters or releasing resources. A finalizer in Visual Basic .NET is always Sub Finalize(), and a destructor in Visual C# is a method with the same name as the class preceded by a tilde (~). Examples of destructors follow: Visual Basic .NET
Public Class aClass Visual C#
public class aClass
Because garbage collection does not occur in any specific order, it is impossible to determine when a class's destructor will be called. Lesson Summary
Lesson 5: Scope and Access LevelsAccess levels define how types are instantiated and how members are accessed. You use access levels to encapsulate data and methods in your types, and to expose functionality to outside objects. In this lesson, you will learn how access modifiers control code access and how to use them in your types.
Estimated lesson time: 20 minutes You can control how elements of your application are accessed by using access modifiers. Access modifiers are keywords such as Public (public), Private (private), and Friend (internal) that precede a variable or type declaration. The keyword that is used controls the level of access the member is allowed. When an access modifier precedes a member declaration, it affects the scope of that member, meaning it controls what code can access it. When a modifier precedes a type declaration, it determines both the scope of its members and how that type is instanced. Member Access Modifiers Type members can have modifiers to control their scope. Table 1.2 summarizes the different access levels. Table 1-2 Access Levels
Any member with the Public (public) modifier is visible to all code outside the class. Thus, other objects can access and modify public fields and can call public methods. Conversely, Private (private) methods are visible only inside the type to which they belong and cannot be accessed from the outside. A third access modifier, Friend (internal), indicates that members can be accessed by other types in the same assembly but cannot be accessed from types outside the assembly. The Protected (protected) modifier allows access from within the type to which the member belongs and to any types that inherit that type. The Protected Friend (protected internal) level provides the union of Protected (protected) and Friend (internal) access. For member variables, the access modifier can replace the Dim statement. If the Dim statement is used (in Visual Basic .NET) or no access modifier is used (in Visual C#), the variable is considered private in Visual C# and Visual Basic .NET classes, Public in Visual Basic .NET structures, and private in Visual C# structures. Methods do not require an access modifier. If no access modifier is specified, the method is Private (private) by default in a class or structure in C#, and Public (public) in a class or structure in Visual Basic .NET.
The following example demonstrates how to use the access modifiers and illustrates how they control access: Visual Basic .NET
Public Class aClass Visual C#
public class aClass Type Access Modifiers Structures and classes can also have access modifiers. Access modifiers control how a type can be instantiated and are similar to access modifiers for members. A Public (public) class can be instantiated by any object in the application. A Friend (internal) class can be created by other members of the assembly but cannot be created by objects external to the assembly. The Private (private) and Protected (protected) modifiers can be used only on nested types. A private class can be created only by objects of its own type or by types in which it is nested. Nested types also can be Protected (protected) or Protected Friend (protected internal), which allows classes inheriting the parent class to have access to them. Protected Friend (protected internal) classes are also visible to other members of the namespace. If no access modifier is specified for a class or a structure, it is considered Public (public).
Access Modifiers for Nested Types In general, a nested type is a type that is used exclusively by the type that contains it. Thus, it is usually a good practice to assign the Private (private) access modifier to a nested type. Under rare circumstances, you might want to create a nested type that can be created by other types and assign it a different access modifier. Although you can assign any access modifier to a nested type, the behavior will never be greater than the access modifier of the type that contains it. Consider the following example: Visual Basic .NET
Friend Class ParentClass Visual C#
internal class ParentClass In this example, the nested class is declared Public (public) but is contained within a class that is marked Friend (internal). Although the nested class is public, it will not be visible to any classes outside the assembly by virtue of the parent class being marked Friend (internal). Thus, the nested class has a practical access level of Friend (internal). Shared (static) Members Regular members are unique to each object instance as shown in the following pseudocode: Visual Basic .NET
Dim Object1 as New DemoClass() Visual C#
DemoClass Object1 = new DemoClass(); The MyField field holds a different value, depending on which instance of the class is referenced. It is also possible to have members that are common to all instances of a class. These members are called Shared (static) members. Only one instance of a Shared or static member can exist, no matter how many instances of a particular type have been created. You can create a Shared (static) field by using the Shared (Visual Basic .NET) or static (Visual C#) keyword. For example: Visual Basic .NET
Public Class Demo Visual C#
public class Demo Even though multiple instances of the Demo class might be instantiated, there will be only one copy of the MyField field. Note that the Shared (static) keyword is not an access modifier; rather, it specifies the member's shared nature. Shared members can still be Public (public), Private (private), Friend (internal), and so on. Methods can be shared as well as fields. Whereas regular methods belong to instances of types, shared methods belong to the type itself. Because shared methods belong to the type itself, they cannot access instance data from any objects. They can only utilize shared variables, variables declared within the method, or parameters passed into the method. Accessing Shared Members Because Shared members belong to the type but not to object instances of a type, they should be accessed using the class name rather than the instance name. Although Visual Basic .NET allows you to access Shared members through the object, there is still only one instance of the Shared members. Visual C# is stricter in this regard and does not allow you to access static members through an object instance. An example is shown in the following code sample: Visual Basic .NET
' This example uses the Demo class from the previous example Visual C#
// This example uses the Demo class from the previous example Because Shared members belong to the type instead of any one instance of a type, it is not necessary to instantiate a type before accessing Shared members. Thus, you can call shared methods or retrieve shared fields before an instance of a type exists. Lesson Summary
Lesson 6: Garbage CollectionThe automatic memory management scheme employed by the .NET Framework is called garbage collection. Memory from objects that are no longer used is traced and reclaimed without any action required by the application. In this lesson, you learn how garbage collection works.
Estimated lesson time: 15 minutes The .NET Framework employs automatic memory management, which means that when an object is no longer being used, the .NET Framework automatically reclaims the memory that was being used by that object. This process is called garbage collection. Consider the following example: Visual Basic .NET
Sub GarbageCollectionExample1() Visual C#
void GarbageCollectionExample1() When this procedure ends, the variable myWidget goes out of scope and the object it refers to is no longer referenced by any application variable. The garbage collector continuously traces the reference tree in the background and identifies objects that no longer have references. When it finds one, such as the Widget in the previous example, it deletes it and reclaims the memory. Because the garbage collector is always running, you do not have to explicitly destroy objects when you are finished with them. The garbage collector is a low-priority thread under normal circumstances. It operates when processor time is not consumed by more important tasks. When memory becomes limited, however, the garbage collector thread moves up in priority. Memory is reclaimed at a more rapid pace until it is no longer limited, at which point the priority of garbage collection is again lowered. This non-deterministic approach to memory reclamation seeks to maximize application performance and supplies a less bug-prone application environment. There is a cost, however. Because of the mechanism by which garbage collection operates, you cannot be certain when an object will be reclaimed. Thus, you have no control over when a class's destructor (Visual C#) or finalizer (Visual Basic .NET) is executed. These methods should not contain code that you rely on being run at a given time. Instead, classes that appropriate expensive resources usually implement a Dispose() method to explicitly free those resources when the class is no longer needed. Circular References Garbage collection also manages circular references, previously a common form of memory leak. Consider the following example: Visual Basic .NET
Class Widget Visual C#
class Widget The Widget class consists of two fields: a ChildWidget field that holds a reference to a Widget object and a Parent field that holds a reference to another Widget object. In this example, a Widget object is created and assigned to the variable GrandParent. This object then spawns another Widget object and assigns it to its ChildWidget field. The Parent variable is also assigned to point to this object. Parent, in turn, creates a third Widget, which is assigned to both the ChildWidget field of Parent and to the Child variable. The Parent field of the Child variable is assigned to Parent, thus creating a reference from Child to Parent. When the GrandParent variable is set to nothing, the Widget objects represented by Parent and Child are left referring only to each othera circular reference. Although circular references can create difficult-to-locate memory leaks in other development platforms, the .NET Framework garbage collector is able to trace and remove such memory leaks. Thus, if a pair of objects are only referenced by each other, they will be marked for garbage collection. Lesson Summary
Lab 1: Classes and Garbage CollectionIn this lab, you will practice creating classes and members, and you will create a demonstration of how garbage collection automatically manages memory. You will create a class that interacts with a pre-made user interface. This class will have a shared variable that keeps track of the number of instances that currently exist in memory. Additionally, you will add code to the constructor and destructor of this class to increment and decrement this variable. You will then create multiple instances of this class and watch as their memory is reclaimed by garbage collection. The solution to this lab is available in the \Labs\Ch01\Solution folder on the Supplemental Course Materials CD-ROM.
Before You Begin There are no prerequisites to complete this lab. Estimated lesson time: 20 minutes Exercise 1.1: Making the Demo Class In this exercise, you will create the Demo class that interacts with the DemoTest project. The DemoTest project is available in the \Labs\Ch01\Partial folder on the Supplemental Course Materials CD-ROM. To make the Demo class
Visual Basic .NET
Public Shared Instances As Long Visual C#
public static long Instances;
Visual Basic .NET
Public Sub New() Visual C#
public Demo()
Visual Basic .NET
Protected Overrides Sub Finalize() Visual C#
~Demo()
Exercise 1.2: Demonstrating Garbage Collection The front end provided in the DemoTest project contains a form that displays two controls: a button and a label. Additionally, there is an invisible timer component that updates the label control every second. You will run the application and observe how instances of your class are created and garbage collected. To create the garbage collection demo
Visual Basic .NET
Dim Counter As Integer Visual C#
int Counter; This code declares two variables, a Counter and a variable of the Demo class. It then enters an iteration loop. One thousand loops are iterated, and in each loop, the aDemo variable is assigned to a new instance of the Demo class. Recall that creating a new instance of Demo will cause the class's constructor to execute, incrementing the shared variable Instances. As the loop ends and restarts, the aDemo variable is assigned to another new instance of Demo, and all the references to the previous instance of the Demo class are released, thus marking the class for garbage collection. This loop will execute 1000 times for every click of the button.
The label now reads "There are 1000 instances of Demo in memory". Wait for a while. After a measurable interval, perhaps even as long as a couple minutes, the label will indicate zero instances again, indicating that the 1000 instances of Demo have been garbage collected and their destructors executed, decrementing the Instances variable. The label did not revert instantly because garbage collection is a relatively low-priority thread under normal circumstances. However, when memory gets scarce, the priority of the thread is increased.
ReviewThe following review questions are intended to reinforce key concepts and information presented in this chapter. If you are unable to answer a question, return to the appropriate lesson and review, and then try the lesson again. Answers to the questions can be found in Appendix A.
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||