Dr. GUI .NET 1.1 #1

Dr. GUI .NET 1.1 #1

 

Revised for Version 1.1 of the Microsoft .NET Framework

May 13, 2003

Summary: Discusses .NET Framework classes and other types, and talks about the members in classes, including the properties. Also demonstrates enumerated types, including flags, and nested classes, and demonstrates a few ASP.NET features: session variables and check box lists, as well as AutoPostBack for text boxes and check boxes. (42 printed pages)

Contents

Introduction and Handy Links
Where We've Been; Where We're Going
Types: the Basic Unit of .NET
Give It a Shot!
What We've Done; What's Next

See the source file for this article.

Introduction and Handy Links

Welcome back to our second article. In case you missed it, check out the first installment, Dr. GUI .NET #0.

Also, you might want to check out the Dr. GUI .NET message board. You can discuss there what you read here...and Dr. GUI himself reads and responds to your posts about these articles (ONLY!) every working day.

You may want to check out Dr. GUI's blog, Dr. GUI's Bits and Bytes, at http://blogs.gotdotnet.com/DrGUI.

The ASP.NET applications are actually running on a server: check out the Cold Rooster Consulting Web site to see all the ASP.NET applications from this series.

.NET on the Road

If you're using ASP.NET and would like to write Web Forms applications that target mobile devices such as cell phones, RIM Blackberry devices, Palm OS devices, Pocket PCs, and Handheld PCs, you're in luck: Microsoft® Visual Studio® .NET 2003 now includes Microsoft® ASP.NET Mobile Controls (formerly known as the Mobile Internet Toolkit).

ASP.NET Mobile Controls enable you to write one set of code for a huge variety of mobile devices (check out the complete list) and have the Microsoft .NET Framework take care of delivering the proper UI to a wide variety of devices. And using it is easy—just select an "ASP.NET Mobile Web Application" project type in Visual Studio .NET to begin your project. Get underway by choosing your language: Microsoft® Visual Basic® .NET, Visual C#®, or Visual J#®. Work with the ASP.NET Mobile Web Application project just as you would any ASP.NET Web Forms project, and deploy it as you usually would. You can even do the initial testing of your applications with Internet Explorer!

For running Microsoft® .NET Framework applications directly on smart devices, you can use the .NET Compact Framework. A note: You get this feature only if you buy Visual Studio .NET 2003, not the bargain-priced single-language editions. And currently only the Pocket PC and Microsoft® Windows® CE .NET-powered devices are supported.

Using Visual Studio .NET

Dr. GUI is pleased to report that Microsoft Visual Studio .NET is highly usable and well worth using—especially for the cool debugger, great auto-complete and syntax-checking features, and integrated Help. And you needn't spend a fortune getting a productive development environment since you can get Microsoft Visual C# .NET, Visual Basic .NET, or Microsoft Visual C++ .NET Standard Edition for only US$109 (an incredible deal).

We didn't use Visual Studio .NET last time so that you'd get a feel for how the .NET Framework and common language runtime works, but it's so easy and fun to use that we'll be using it from here on out.

We'll provide the code in an accompanying HTML page, so you can copy it into Visual Studio .NET or run it as on your own like last time if you prefer downloading the .NET Framework SDK for free. (If you're doing only ASP.NET applications and don't mind not having Intellisense and a debugger, we'll discuss how to use Web Matrix, which is free, in a section below.) But if you can afford to, Dr. GUI highly recommends getting at least a standard edition so that you can enjoy the power and ease-of-use of what the good doctor believes to be the best development environment available anywhere.

How to run console applications with Visual Studio .NET

It's easy to run the console applications the good doctor provides here in Visual Studio .NET. Doing so just takes four easy steps:

  1. Copy the text for the program you want to run from the article or source file to the clipboard.
  2. In Visual Studio .NET, create a new Console Application project in the language of your choice. To do this, on the File menu, click New, click Project, select the language, click the Console Application icon, and type an appropriate name for the project.
  3. In the source code file, press Ctrl+A to select all the text Visual Studio .NET wrote for you, and then paste the text you copied to the clipboard in step 1 into the source file, replacing the previous contents.
  4. To debug your program, press F5 or click Play; you can run it without debugging by pressing Ctrl+F5 or by clicking the exclamation point (!) button. (If you don't have the exclamation point button, you may want to add it to your toolbar.)
    Note   If you're using Visual Basic .NET, you'll get an error message about Main not being found. Double-click the message and select the correct module in the dialog box that comes up.

As you see, it's easy to run programs.

If you already have the code in a separate file, you can do the following:

  1. Create a new project in Visual Studio .NET as in step 2 of the procedure on running console applications above.
  2. To delete the source file that Visual Studio .NET generated for you, , right-click the source file in the Solution Explorer window, and on the context menu, click Delete.
  3. Copy the source file to the project directory. Be sure to copy the file rather than move it. You can find the project directory for Visual Studio's current project by right-clicking on the project (NOT on the solution—the project is a child of the top solution node) and selecting properties—or by clicking on the project while the properties window is open.
  4. On the File menu, click Add Existing Item..., or right-click the project name, and on the context menu, click Add Existing Item... to add the file to your project.
  5. Run your project as in step 4 in the procedure on running console applications above.

How to run ASP.NET Web applications with Visual Studio .NET

The procedure for running an ASP.NET Web application with Visual Studio .NET is pretty similar to the preceding procedure except that you create an ASP.NET Web application instead of a console application, you add only the ASPX page to the project, and you have to set the starting Web page for the application.

Note that we're using only Visual Basic .NET for ASP.NET applications from here on out. To create an ASP.NET Web application:

  1. In Visual Studio .NET, on the File menu, click New, click Project, select the language of your choice, click the ASP.NET Web Application icon, and type in your project name.
  2. In the Solution Explorer window, right-click the WebForm1.aspx default Web page, and on the context menu, click Delete.
  3. Copy the source files into your project directory. Be careful to copy, not move, the files. (As an alternative, you could create a new text file in the project directory using Notepad, then paste text into Notepad and save the file with the proper name.) You'll need at least the ASPX file for the Web page and the code-behind file. Typically, the code-behind file is named the same as the ASPX file with the extension for the language added to the end. For instance, if the ASPX file is named InstanceSharedASPX.aspx, the code-behind file would be named InstanceSharedASPX.aspx.vb.
  4. On the File menu, click Use Add Existing Item..., or right-click the project name, and on the context menu, click Use Add Existing Item... to add only the ASPX file to your project. Change the file type to Web Files at the bottom of the dialog box.
  5. In the Solution Explorer window, right-click the page you just added, and on the context menu, click Set As Start Page to make it the application's start page.
  6. To debug your program, press F5 or click the Play button, or, to run it without debugging, press Ctrl+F5 or click the exclamation point (!) button.

Using Microsoft ASP.NET Web Matrix

Web Matrix is a free IDE you can use for writing ASP.NET applications. If you only want to run the ASP.NET applications with these articles, you can use Web Matrix. Dr. GUI personally prefers to use Visual Studio because only Visual Studio has IntelliSense, a debugger, and a really fast help system with a better index (Web Matrix uses MSDN Online as its help system). Those features make Visual Studio well worth the money.

Another problem with Web Matrix is that it isn't designed to support code-behind pages in the same way Visual Studio does. As a result, you'll have to make some edits to the ASPX files, since they were created by Visual Studio. We'll discuss what the edits are below.

Finally, Web Matrix sometimes changes the HTML formatting in some bad ways, so your application may not look exactly the same as Dr. GUI's. For instance, Web Matrix changes newline characters to spaces wherever it wants. This normally isn't a horrible thing, but it also does it to text within a <PRE></PRE> tag—doing this changes the appearance of the text, since newline characters are significant within that tag.

Because of these problems, Dr. GUI is going to stick with Visual Studio. But the price of Web Matrix (free) might be hard for you to pass up, so if you want to use Web Matrix to work on the Dr. GUI .NET series, here's how:

  1. If you'd like to use copy and paste rather than copying files, create the ASPX file by clicking New on Web Matrix's File menu. Select "ASP.NET Page," set the directory, and type a file name. If you want to copy the files, skip to step 4.
  2. In the new file window, click the All button at the bottom, then press Ctrl+A to select all the existing text, then paste your text in its place. Save this file.
  3. Repeat steps 1 and 2 for the Visual Basic code behind file. Be sure to select "Class" rather than "ASP.NET Page" in step 1 and fill out the rest appropriately. It doesn't matter what you type for Class and Namespace since we're going to replace the code anyway. Save this file, then skip to step 5.
  4. If you want to copy files, just copy them to any directory and open the ASPX file from within Web Matrix.
  5. When you open the ASPX file, be sure to click the All tab at the bottom of the file's window so you display all of the code.
  6. You'll have to make changes to both files. Modify the <%@ Page ... %> tag at the top of the ASPX file as follows: First, change the CodeBehind="..." attribute to read Src="...", then delete the namespace and the dot following the namespace from the value of the Inherits attribute, but leave the class name. So if the tag was originally:

    <%@ Page Language="vb" AutoEventWireup="false"
    Codebehind="default.aspx.vb" Inherits="SomeASPX.WebForm1" %>

    you would change it to:

    <%@ Page Language="vb" autoeventwireup="false" Src="default.aspx.vb"
    Inherits="WebForm1" %>

  7. You will probably need to add the Imports System statement as the first line of the Visual Basic file. When you're done, be sure to save the file! (Since Web Matrix has no concept of projects at all, you have to be sure to save any files that you don't directly run—and you can only directly run ASPX files.)
  8. Switch back to the ASPX file and run the program by pressing the Play button, and then choose whether to use the ASP.NET Web Matrix Web server or IIS. Note that if you use the ASP.NET Web Matrix Web server, be sure to stop it when you're done.

Where We've Been; Where We're Going

Last time, we talked about what .NET is—specifically about the .NET Framework and runtime—and why you might care. Then, we talked about how to get and install the .NET Framework SDK and/or Visual Studio .NET and a few simple programs for both console and web pages were shown, both in C# and Visual Basic .NET, at the command line. Finally, we took a look at the metadata and intermediate language (IL) for these programs, and spent a little time looking through the documentation. If you missed all this, you can read all about it in the previous Dr. GUI .NET article.

This time, we're going to discuss the basic unit of the .NET Framework: types, including discussing the built-in types, classes, and value types.

Types: the Basic Unit of .NET

If you've looked at the .NET Framework much, you'll notice that it's full of types called classes, interfaces, enumerations, and structures (also known as value types). Most of the types in .NET are classes—in fact, in most .NET languages, you need to create a class just to write a "Hello, world!" program, because the Main function needs a class of which it can be a member.

Now, it's true that the .NET Framework itself actually supports global functions and data, and you can write global functions in C++ using the Managed Extensions for C++. But doing so isn't necessarily cross-language compatible, so most .NET languages don't support globals. For more on what you can and can't do across languages, look up the Common Language Specification (CLS) in the .NET Framework documentation. Types and methods that can be used across languages are said to be "CLS compliant."

Anyway, your basic programming task when programming for the .NET Framework is to create new types (usually new classes) and to write the members (mostly methods, with a little bit of private data) of those classes.

Classes

A class in the .NET Framework is basically the same thing as a class in any object-oriented language: It's a template for creating objects that "contain" both data and the methods that operate on that data. As such, it defines a new data type, defined in terms of the operations you can perform on it. Each type is an abstraction of some concept. Note that in good object-oriented design, the type is defined by the set of operations, not the format of the internal data. In fact, you usually make the data private so that other parts of the program can't break the abstraction.

It's important to create meaningful abstractions with clear, meaningful operations. Dr. GUI once had quite the argument with a programmer who decided that, since "point" and "size" both contained two integers, "size" must be redundant, so he eliminated it from the class library. This led to such atrocities as "setSize" taking a "point" as an argument and "getSize" returning a "point."

The reason combining the two types is wrong is because the operations you perform on a point, such as moving it, are very different from operations you perform on a size, such as inflating or deflating it. Because the abstractions represent two different sets of operations, they should be two distinct classes, despite the fact that their data representation might be identical.

Abstraction examples

For instance, the floating-point types Single and Double (float and double in C#) are both abstractions of the set of real numbers. What's the data format? Usually you don't even want to know, and you almost never should have to know.

The operations that Single/float and Double/double support (addition, subtraction, and so on) are the same operations you perform on real numbers. Yet, these floating-point types are limited in range and in precision—they're abstractions, not the real thing.

Note that you can't perform bit-wise arithmetic (AND/OR/NOT/shifting) on floating-point types—those operations aren't part of the abstraction, so they're not supported for floating-point types.

However, you can perform such operations on integer types such as Int32 (int in C#, Integer in Visual Basic .NET), because the integer types are abstractions of computer memory words of various sizes. The operations you can perform on them are similar to what you can do in assembly language to a machine word—not only arithmetic operations but also logical operations. In fact, you could make a good case that despite the names int or Integer (and so on) these types aren't so much an abstraction of the mathematical set of integers as they are abstractions of machine words of various sizes.

You can create more complicated abstractions: points, sizes, lines, rectangles, lists, hash tables, employees, animals, windows, brushes, database connections, queries, XML documents, and so forth. For each one you create, you'll determine an appropriate set of operations for the abstraction and then figure out what the internal data representation needs to be. Or, if you're lucky, someone will have created a great abstraction (type) that you can use rather than writing your own.

In the same way, types you create are often abstractions of some data type and, while keeping the data representation private, expose a set of operations.

Classes vs. objects and operator new

So how do you create a class? You declare it, all in one place (unlike C++ where you typically separate the declaration of a class from its implementation), after the word class. For instance:

C#

   class HelloWorld {
      // ...members go here, discussed next
   }

Visual Basic .NET

   Class HelloWorld
      ' ...members go here, discussed next
   End Class

That gives us the outline of a template for creating objects (we'll need at least one method or field, as described just ahead). Given a class, how do we get an actual object (or, to put it another way, an instance of the class)? That's easy: We normally call the new operator:

C#

   // This is in some method
   HelloWorld hw = new HelloWorld();

Visual Basic .NET

   ' This is in some method
   Dim hw As HelloWorld = New HelloWorld()
   ' or: Dim hw As New HelloWorld()

Once we've done this, hw refers to a HelloWorld object that's been created in the garbage-collected heap, and you can access any of its members by using the hw reference. Note that the parentheses are required; if you have parameters to be used in initialization, you can pass them in the parentheses.

Members

Classes in .NET have members. In traditional object-oriented programming, there are two main types of members: data members, or fields; and function members, or methods (sometimes including constructors). The .NET Framework adds two additional types of members: properties and events. And you can have types nested within your type. We'll also briefly talk about some special kinds of members in the C# language that don't appear in the .NET Framework.

Fields

Fields store data. There are two types: instance and static (Shared in Visual Basic .NET), with instance being the default. With instance fields, each object of that class has its own copy of the field—there's one per instance; thus the name. With static/Shared fields, there is only one copy of the field shared by all instances of the class—so if one object of that type changes the value, it changes for all the objects of that type. Here's an example of fields in C# and Visual Basic .NET:

C#

   class Test1 {
      int instanceField; // note we use lower-case first letter...
      static string staticString; // ... because they're private
      // ...we'll show how to access later...
   }

Visual Basic .NET

   Class Test1
      Dim instanceField as Integer ' note we use lower-case first... 
      Shared staticString As String ' ... letter because they're private
      ' We'll show how to access later...
   End Class
      
   

You'll note that the fields we've declared here are all private (by default). You should almost never use public fields. Making the fields public would allow your class's users to change your data without your class knowing, likely creating bugs. Properties provide a great way to give users the convenience of field-access syntax with the safety and encapsulation of access methods.

Methods

A method is a function that's part of a class. Methods may also be instance or static (Shared in Visual Basic .NET). Again, instance is the default except in a Visual Basic .NET Module.

An instance method works on a particular instance of an object. It implicitly receives a reference to the object on which it's working. You can access this reference implicitly by just using the name of the member (as in "Bar()") or explicitly with the keyword this (Me in Visual Basic .NET). So you can call the method Bar by saying either simply "Bar()" or "Me.Bar()" in Visual Basic .NET, /"this.Bar()" in C#.

(By the same token, when you access a field within a method in the class in which the field is declared, you have your choice of using this/Me or simply using the field name.)

You can also call instance methods with a reference to a particular object, as in SomeObject.Method(). When you do this, the reference to the object becomes the this/Me reference in the called method.

Static methods do not receive a this/Me pointer; therefore, they cannot access any instance data in the class (unless they have or get a reference to some object—for instance, through a parameter to the method). That's why they must be called using the class name, as in SomeClassName.StaticMethod(). They are often used to access static data; they're also used to simulate global methods in other systems.

In the .NET Framework, methods must generally be members of some class. (The Common Language Specification, or CLS, requires this for cross-language compatibility.) Static methods are a way of making your methods like global methods in that you can call them without first having to instantiate an object, since they don't require a this/Me pointer. Instead, you just use the class name and the method call, as in "System.WriteLine("Hello, World!") or x = Math.Sin(y)".

As we've seen, System.Console.WriteLine is an example of a static method (note that "Console" is a class name and that we didn't have to create an object of type Console to call WriteLine), as are most of the methods in the System.Math class, such as Sin, Cos, Exp, etc.

Here's an example of some static/Shared and instance methods:

C# (see the code)

// compile with: csc InstanceStaticCS.cs
using System;
class Test1Continued {
   int i = 5;
   static int s = 4;
   public void InstanceMethod() {
      Console.WriteLine("InstanceMethod--i: {0}, s: {1}", i, s);
   }
   static public void StaticMethod() {
      // can't access instance data
      // (but could if we created an object as in Main)
      Console.WriteLine("StaticMethod--s: {0}", s);
   }
}
class TestTest1Continued { // a different class
   public static void Main() {
      Test1Continued.StaticMethod();
      Test1Continued tester = new Test1Continued();
      tester.InstanceMethod();
      Console.ReadLine();
   }
}

Visual Basic .NET (see the code)

' compile with: vbc InstanceSharedVB.vb
Class Test1Continued
    Dim i As Integer = 5
    Shared s As Integer = 4
    Public Sub InstanceMethod()
        Console.WriteLine("InstanceMethod--i: {0}, s: {1}", i, s)
    End Sub
    Public Shared Sub SharedMethod()
        ' can't access instance data
        ' (but could if we created an object as in Main)
        Console.WriteLine("SharedMethod--s: {0}", s)
    End Sub
End Class
Class TestTest1Continued ' a different class
    Public Shared Sub Main()
        Test1Continued.SharedMethod()
        Dim tester As New Test1Continued()
        tester.InstanceMethod()
        Console.ReadLine()
    End Sub
End Class

Methods default to private, meaning they can only be called from within the class in which they're declared; we declare them public/Public here so we can access them from any class.

Methods can be overloaded: You can have more than one method with the same name, provided each has different types in its parameter list. The compiler (and when doing late binding, the .NET runtime), figures out which of the methods to call based on the types of the parameters you pass. So, you can have Foo() that takes no parameters, that takes an int/Integer, and that takes an int/Integer and a double/Double (for instance)—and can call the appropriate Foo method with no parameters, with an int/Integer, or with an int/Integer and a double/Double. (You can call with certain other types of parameters, too, since there are some conversions possible; for now, Dr. GUI doesn't want to get into it, especially since the conversion/function overload matching rules vary a little from language to language in some cases.)

There are a bunch of other modifiers for methods; we'll discuss most of them when we talk about inheritance. Dr. GUI will tell you about extern now, though: it is used when a method is declared in C# but implemented outside of the managed .NET Framework, such as in a native-code Microsoft Windows® DLL. (In Visual Basic .NET, you use the Declare statement or the DllImport attribute to do the same thing.) Note that using existing unmanaged code is easy in the .NET Framework (assuming your code has appropriate security permissions to do so), unlike in Brand J.

The ASP.NET application for this example is quite similar, except the Console.WriteLine output would never get to the Web page, so we need to write the output into a label. If you like, you can run this application.

Here's the ASPX page for the application. Note that this was written using Visual Studio .NET, so it uses tags somewhat differently from the stand-alone applications we wrote last time. You can see this code.

<%@ Page Language="vb" AutoEventWireup="false" 
            Codebehind="InstanceSharedASPX.aspx.vb" 
               Inherits="InstanceSharedASPX.WebForm1"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<HTML>
   <HEAD>
      <title>Dr. GUI .NET #1: Instance/Shared Demo</title>
      <meta content="Microsoft Visual Studio.NET 7.0" name="GENERATOR">
      <meta content="Visual Basic 7.0" name="CODE_LANGUAGE">
      <meta content="JavaScript" name="vs_defaultClientScript">
      <meta content="http://schemas.microsoft.com/intellisense/ie5" 
            name="vs_targetSchema">
   </HEAD>
   <body>
      <H1><A href="http://msdn.microsoft.com">Dr. GUI .NET #1</A>: 
            Instance/Shared Demo</H1>
      <FORM id="Form1" method="post" runat="server">
         <P></P>
         <asp:button id="Button1" runat="server" Text="Call Shared 
            method"></asp:button>
         <asp:label id="Label1" runat="server"></asp:label>
         <P></P>
         <asp:button id="Button2" runat="server" Text="Call instance 
            method"></asp:button>
         <asp:label id="Label2" runat="server"></asp:label>
      </FORM>
   </body>
</HTML>

And here's the code behind (see this code):

Imports System.Web.UI.WebControls

Public Class WebForm1
    Inherits System.Web.UI.Page
    Protected WithEvents Label2 As System.Web.UI.WebControls.Label
    Protected WithEvents Button2 As System.Web.UI.WebControls.Button
    Protected WithEvents Label1 As System.Web.UI.WebControls.Label
    Protected WithEvents Button1 As System.Web.UI.WebControls.Button

#Region " Web Form Designer Generated Code "

    'This call is required by the Web Form Designer.
    <System.Diagnostics.DebuggerStepThrough()> _
        Private Sub InitializeComponent()

    End Sub

    Private Sub Page_Init(ByVal sender As System.Object, _
        ByVal e As System.EventArgs) Handles MyBase.Init
        'CODEGEN: This method call is required by the Web Form Designer
        'Do not modify it using the code editor.
        InitializeComponent()
    End Sub

#End Region

    Private Sub Page_Load(ByVal sender As System.Object, _
        ByVal e As System.EventArgs) Handles MyBase.Load
        'Put user code to initialize the page here
    End Sub

    Private Sub Button1_Click(ByVal sender As System.Object, _
        ByVal e As System.EventArgs) Handles Button1.Click
        Test1Continued.SharedMethod(Label1)
    End Sub

    Private Sub Button2_Click(ByVal sender As System.Object, _
        ByVal e As System.EventArgs) Handles Button2.Click
        Dim tester As New Test1Continued()
        tester.InstanceMethod(Label2)
    End Sub
End Class

Class Test1Continued
    Dim i As Integer = 5
      ' Don't use shared fields in ASP.NET; this is for
      ' a demo ONLY!
    Shared s As Integer = 4 ' Don't use Shared in ASP.NET
    Public Sub InstanceMethod(ByRef l As Label)
        'Console.WriteLine("InstanceMethod--i: {0}, s: {1}", i, s)
        l.Text = "InstanceMethod--i: " + i.ToString() + ", s:  " _
            + s.ToString()
    End Sub
    ' Shared methods are OK
    Public Shared Sub SharedMethod(ByVal l As Label)
        ' can't access instance data
        ' (but could if we created an object as in Main)
        l.Text = "SharedMethod--s: " + s.ToString()
    End Sub
End Class

You'll note that our class is almost identical to the previous one, with the exception that it does output by setting a label rather than using Console.WriteLine. (Console.ReadLine is even worse: it'll hang your application until you close the browser, since there's no way to press Enter since there's no console!) And you'll note the button click handlers, just like last time.

An important note: while it's just fine to use static/Shared methods in ASP.NET applications, you should NOT use static/Shared fields. Shared fields will be shared in an unpredictable manner across instances of your ASP.NET application (in other words, across sessions). This tends to make a real mess, so just don't do it, even if you think you know what you're doing. Instead, use view state or session state, which work predictably and reliably all the time.

Note that the values of instance fields are not kept between event handlers. In order to store values so the next handler can access them, you'll need to store session state. Fortunately, ASP.NET makes this extremely simple—as we'll see in the next application.

Creating the object: Constructors

Each class has at least one method called a constructor. If you don't provide any constructors, the compiler will provide one for you that takes no parameters and does nothing except to call the overload of your base class's constructor that takes no parameters.

In C#, constructors are declared as a method named the same as the class, but with no return value. In Visual Basic .NET, constructors are named New rather than the class name.

Constructors can be overloaded, just the same as any method. Each constructor will call a constructor (by default, the one with no parameters) in your base class—in Object if you have no explicit base class. This call comes before your constructor body executes—this way, you know that your base class(es) are initialized properly before your constructor's code executes.

The constructor is only called once per object—and only at the time the object is created. Its primary job is to initialize the object's instance fields so the object is ready to use. (There is also a static constructor, primarily for initializing static fields. But let's skip that for now.)

Clean up when you're done: "Destructors," IDisposable/Dispose, and Finalize

Destructors are used by some object-oriented systems, especially C++, to clean up objects just before they're destroyed. The usual purpose of a destructor is to free resources that the object controls. Often these resources are just memory. Since the .NET Framework uses automatic garbage collection, freeing managed memory from a destructor is unnecessary. As a result, you usually don't need a destructor at all.

However, it's also possible for your object to control unmanaged resources such as database connections, file handles, and window handles. Normally you want to free these unmanaged resources as soon as possible after the object controlling the resource is not longer needed; thus, we'd like to have a way to control just when the code to free the unmanaged code runs.

The .NET Framework does not support C++ style destructors; specifically, it does not support deterministic destruction, where you know exactly when each object's destructor is called. However, the .NET Framework does support standard explicit clean up through IDisposable's Dispose method. Just call Dispose when you're done with the object to release the unmanaged resources the object holds. Many of the classes in the .NET Framework—such as any class that uses a graphical object such as a window or a font, a file or socket handle, or a database connection—implement IDisposable. (See the complete list of classes in the .NET Framework SDK.)

It's usually easy to call Dispose properly, but it's a bit trickier to actually write a proper Dispose method (and associated methods). Check out the design pattern for details on how to write Dispose and associated methods properly for objects that control unmanaged resources.

You will call Dispose frequently, but you'll only rarely have to write your own class that controls an unmanaged resource and therefore needs the tricky implementation of Dispose and friends. This is a good thing.

Using using

By the way, C# supports a scheme to provide an automatic deterministic call to a Dispose; it's called the using statement. Check it out in the C# Language Specification documentation.

The using statement is defined in terms of a try-finally block that contains your code and a call to Dispose. For instance, see the following:

C#

using (SomeType t1 = new SomeType()) {
   t1.SomeMethod();
   // and perhaps other stuff, before or after
}

is exactly equivalent to:

SomeType t1 = new SomeType();
try {
   t1.SomeMethod();
   // and perhaps other stuff, before or after
}
finally {
   if (t1 != null) ((IDisposable)t1).Dispose();
}

...written without a using statement. Note that SomeType MUST implement the System.IDisposable interface (and, of course, its Dispose method).

A warning: some programmers have been known to use using statements to simulate the with statement in some languages other than C#. Don't do this—they're not the same, since in no language does a with statement generate a call to Dispose for you automatically. Misusing using can easily result in hard-to-find bugs as you later attempt to use objects that have already had Dispose called on them.

Since Visual Basic .NET doesn't have a using statement, you have to write your own code (similar to the second snippet) to deal with calling Dispose properly. (This is a major bummer.) The two C# segments above are equivalent to the following in Visual Basic .NET.

Visual Basic .NET

Dim t1 as new SomeType()
Try
   t1.SomeMethod()
   ' and perhaps other stuff, before or after
Finally
   If (Not (t1 is Nothing)) Then CType(t1, IDisposable).Dispose()
End Try

You can of course simplify the code by removing the If check if you're one hundred percent sure that t1 will never be null/Nothing, and you can even eliminate the try/finally if you're one hundred percent sure that the methods you call will never throw an exception.

If you forget to call Dispose in any language, it's not the end of the world. The unmanaged resources used by the object will eventually be released when the object is garbage collected. But that could be quite a while, so you might run into performance problems (not to mention resource contention problems) if you don't use call Dispose properly, so while you're learning, get into the habit of doing the right thing: when you're done with the object, call Dispose on objects that support it.

Finalizers

The .NET Framework also supports totally automatic clean up by supporting a method in all objects called Finalize that will automatically be called before the object is garbage collected. (Note that you cannot control exactly when the object is garbage collected, so you can't control exactly when Finalize is called. That's why we have IDisposable.)

You normally don't need to write a destructor or Finalize method unless your class controls some unmanaged resource. And if your class does control some unmanaged resource, you should do the right thing, writing not only Finalize but also Dispose, following the design pattern for IDisposable/Dispose/Finalize in the .NET Framework General Reference.

Note that if you do write a Finalize method, you should always call your base class's Finalize method from yours. But, again, you should rarely write Finalize methods unless your object needs to release some system resource, such as a handle to a window or file, network socket, or database connection. Unnecessary use of Finalize slows the system down by requiring the runtime to do more work and delay garbage collection. So, let the garbage collector automatically take care of any objects that are referred to by your object. You don't need to do anything to free them up.

C# supports something called a destructor with a syntax similar to C++ but without the C++ deterministic destruction semantics: when you write a destructor, the compiler simply generates a Finalize method that automatically calls your base class's Finalize method after it executes the code you write in the method (so you don't have to remember to make the call to the base class yourself). You can check out the generated code by using ILDASM. Note that you cannot directly write a Finalize method in C#—you instead write a destructor, which generates your Finalize method behind the scenes.

Properties (NEW!)

If you're used to programming in Visual Basic, you're used to properties. In the .NET Framework, including C# and Visual Basic .NET, properties are a little bit different than in old Visual Basic for Windows in that they're always implemented as get/set methods, never as data fields. They're very much like the properties in the Microsoft Visual C++ COM extensions in Visual C++ 6.0. Properties are methods that you call using field-access syntax, giving the best of both worlds: the safety and power of methods and the clean syntax of fields.

Basically, the idea is this: Properties convert syntax that looks like field access into a method call. Let's see why this is important:

For instance, if you have a class called Person that has a public Integer field called Age, declared as follows (in Visual Basic .NET only for brevity...):

Class Person
   Public Age as Integer
   ' and other stuff...
End Class

Then your C# users could read and set the field with the following syntax:

Person Jim = new Person(); // create Person object; pointed to by Jim
Jim.Age = 25
int JimsAge = Jim.Age; 
Jim.Age = -5; // invalid, but unchecked if you use a field

And your Visual Basic .NET users would use this syntax:

Dim Jim as New Person()   ' create Person object; pointed to by Jim
Jim.Age = 25
Dim JimsAge as Integer = Jim.Age
Jim.Age = -5   ' invalid, but unchecked if you use a field

There are two problems with this. First, your users now know details of your internal data representation. This can lead them to take liberties with your object by doing things you didn't intend to allow in the interface. Second, the users could change the data to an invalid value—perhaps too large, or zero, or negative. And they could do so without your object knowing. In this case, if Age was set to zero and we calculated IQ by dividing by age, we'd get a division by zero error.

In languages like C++, you take care of these problems by providing public accessor methods, probably called GetAge and SetAge. You would also have a private int/Integer field called age (note the lower-case "a "for private field names), which is read and written by the accessor methods. This works, but the syntax for accessing the age is the much less-natural and less-elegant method syntax (here in C#):

   Jim.SetAge(25);
   int JimsAge = Jim.GetAge();

...and here in Visual Basic .NET:

   Jim.SetAge(25)
   Dim JimsAge as Integer = Jim.GetAge()

But there's a tremendous advantage to using accessor methods: First, you don't expose your internal data structure to the world. Second, your SetAge method can guard against setting the age to zero or to a negative number. Or it can do something special when the object's value changes, such as repaint the screen if it's a visual object, or fire an event to some other object. (For instance, you could fire a HappyBirthdayEvent to an object that buys cake and sends birthday cards.)

Properties: the best of both worlds

Properties allow you to have the advantage of direct field access's more elegant syntax along with the robustness and better encapsulation of accessor methods. The syntax for implementing and using a property in C#, along with a constructor, is shown here (you can see this code):

// compile with: csc PropertiesCS.cs
using System;
public class Person
{
   int age; // note that this is private; See Note 1
   public Person(int age) 
   { // constructor, See Note 2
      this.age = age; // this. disambiguates!
   }
   public int Age 
   { // property; See Note 1
      get 
      { // See Note 3
         return age;
      }
      set 
      {      // validating value, See Notes 3, 4
         if (value > 0 && value < 150) 
         {
            age = value;
         }
         else 
         {      // throw exception if invalid value
            throw new ArgumentException(
               "Age must be between 1 and 150");
         }
      }
   }
}
class TestPerson 
{
   public static void Main()
   {
      Person Jim = new Person(25); // See Note 5
      Console.WriteLine("This year, Jim was {0} years old", Jim.Age);
      Jim.Age++;      // uses both get and set to do increment, See Note 6
      Console.WriteLine("Next year, Jim will be {0} years old", Jim.Age);
      Console.ReadLine(); // wait before closing
   }
}

The syntax in Visual Basic .NET is similar (see this code):

' compile with: vbc PropertiesVB.vb
Imports System
Public Class Person
    Dim ageValue As Integer ' name must change, See Note 1
    Public Sub New(ByVal ageValue As Integer) ' constructor, See Note 2
        Me.ageValue = ageValue ' Me. disambiguates!
    End Sub
    Public Property Age() As Integer ' See Note 1
        Get ' See Note 3
            Return ageValue
        End Get
        Set(ByVal Value As Integer) ' See Note 3
            ' validating value, See Note 4
            If Value > 0 And Value < 150 Then
                ageValue = Value
            Else ' throw exception if invalid value
                Throw New ArgumentException( _
                 "Age must be between 1 and 150")
            End If
        End Set
    End Property
End Class
Class TestPerson
    Public Shared Sub Main()
        Dim Jim As New Person(25) ' See Note 5
        Console.WriteLine("This year, Jim was {0} years old", Jim.Age)
        Jim.Age += 1 ' uses both get and set, See Note 6
        Console.WriteLine("Next year, Jim will be {0} years old", _
               Jim.Age)
        Console.ReadLine()  ' wait before closing
    End Sub
End Class

Some things to note in the preceding code:

Note 1: In the C# code, we could name the private field age and the property Age, since C# is case sensitive. It's not CLS-compliant to distinguish names only by case, but here it's okay because the field is private and CLS compliance applies only to the non-private parts of the class. Visual Basic .NET is not case sensitive, however, so it requires names that differ by more than just case—that's why we used "Age" and "ageValue".
Note 2: However, in the constructor (named Person in C#, New in Visual Basic .NET), we named the parameter the same as the private field—both called "age" ("ageValue" in Visual Basic .NET). We used the this/Me pointer to distinguish between the two. This is very common in C# and Visual Basic .NET programming of constructors and set methods.
Note 3: The get method of the Age property uses the return value to specify what value to use for the value of the property. In C#, the set method on the Age property uses the keyword value to indicate the value to which the property is being set. In Visual Basic .NET, the value to which the property is being set is passed explicitly as a parameter.
Note 4: In the set method, we check the value being set and throw an exception if the value is out of range. Our caller can catch the exception if it likes; if they don't, the program will terminate. We'll talk about exceptions in a future column.
Note 5: We used new/New to create a new object. The constructor is called before new/New returns, initializing the age to 25. (It was 24 in the 1.0 version of the article, but Jim has aged.)
Note 6: In C#, the increment operator both gets and sets the object—it gets the value, increments it, and sets it to the new value. In Visual Basic .NET, there is no increment operator, but we use the "X += 1" syntax to increment the property.

While we're on the subject, Dr. GUI wants to share some advanced information about properties. First, you can create read-only or write-only properties in C# by simply omitting one of the get or set methods. (Don't try to omit both!) In Visual Basic .NET, you have to use one of the ReadOnly or WriteOnly keywords as well.

Second, since properties are implemented as methods, they can be static/Shared—or abstract/MustInherit or virtual/Overridable (more on this later). Third, although C# doesn't support it, properties can have parameters, although we won't go into this here. Visual Basic .NET supports parameterized properties—just add parameters in the appropriate places when you define the properties and when you use them.

Lastly, if you check the metadata for these programs, you'll see that the property is implemented as the get_Age and set_Age methods, along with some special property metadata. The fact that the methods have fixed names means they can be used with languages that don't support properties directly.

The ASP.NET application can use the exact same Person code, since it does no I/O. (We did have to add an attribute—see below.) Once you add the attribute, you can even put the Person class in a separate file and use it in both ASP.NET and console projects if you want, although we won't this time.

Rather than reproducing the entire ASPX file for the Web page, let's just look at the form (you can see this code):

    <form id="Form1" method="post" runat="server">
        Jim is now <asp:Label id="Label1" runat="server"> 
        </asp:Label> years old.<p></p>
        <asp:Button id="Button1" runat="server" 
            Text="Birthday time!"></asp:Button>
    </form>

You can run this application.

Note that the label control appears in the middle of text, so we'll just set the label to the appropriate string (Jim's current age) and let ASP.NET do the rest. That's a lot simpler than the string concatenation we did in the other examples—that's the power of HTML!

When the button is clicked, the click handler in the code below will be called. Let's look at that code, less the Person class (which is exactly the same as above) and less the stuff generated for us by Visual Studio .NET (you can see this code):

Dim Jim As Person

Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
    Jim = ViewState("Jim")
    If Jim Is Nothing Then Jim = New Person(25)
    Label1.Text = Jim.Age.ToString()
End Sub

Private Sub Button1_Click(ByVal sender As System.Object, 
            ByVal e As System.EventArgs) Handles Button1.Click
    Jim.Age += 1
    Label1.Text = Jim.Age.ToString()
    ViewState.Add("Jim", Jim)
End Sub

' must add "Serializable" attribute to save state in ASP.NET apps
<Serializable()> _
Public Class Person
   ' ...

We only had to write two methods, add the Serializable attribute to our Person class, and declare one variable. The primary function of the two methods is to set the label to the proper value.

As mentioned before, the values of instance variables are destroyed between events—so if we attempted to rely on Jim being around in the button click handler, we'd be disappointed.

Luckily, ASP.NET makes it almost trivial to save objects between events on your page. You can see how we do this at the end of the button click handler: we just use one line:

ViewState.Add("Jim", Jim)

This writes the state of the Jim object into the view state for our page. (If our application spanned multiple ASPX pages, we'd have to use session state rather than view state. We could use session state for this single-page application as well, but we didn't because using view state is more efficient for single-page applications where the data is small.) Each user who runs the application gets a separate view state, so there's no interference. The string "Jim" is the key for getting this object back.

A note: View state is stored as text in a hidden input field, as we saw in Dr. GUI .NET #0. What's new here is that we can add our own data to this view state, as well as retrieve it.

The process of converting an object to some form that can be stored is called "serialization." The .NET Framework has a rich, flexible, and extensible serialization framework, but it's not entirely automatic— at minimum, we have to tell the .NET Framework what types of objects we'd like to be able to serialize.

In order to save the state of a Person object, all we have to do is declare the Person class with the Serializable attribute, as shown above. All this attribute does is to add a little metadata to the Person class's metadata. The .NET Framework sees this and takes care of all the rest.

Note that we didn't have to do anything else in order to be able to write this object out. ASP.NET took care of figuring out the data format of the Person class (using the metadata) and writing the object out properly. Cool, huh?

Reading it back in is almost as trivial: In the page load handler, just use the following code:

Jim = ViewState("Jim")
If Jim Is Nothing Then Jim = New Person(25)

The first line creates the Jim object from the information stored in the view state. Again, this is all automatic. The runtime took care of creating the proper type of object and initializing it from the data.

But it's possible that Jim hasn't been stored—it might be the first time we've visited this page, or perhaps we haven't clicked the button. (Note that the code is optimized to only save the state when it changes from the default value.) So we need to check the reference we get back to make sure it's not Nothing (null in C#). If it is Nothing, then we need to create and initialize an object from scratch.

Events (NEW!)

In Visual Basic for Windows and in COM, objects can notify listeners of something interesting by firing an event. In COM, events are very complicated—thankfully, Visual Basic hides this complication. There are many types of components that need to fire events, ranging from buttons that fire events when they're clicked to objects that fire events when their data has changed.

The concept of an event and the infrastructure to support events is built into the .NET Framework, so declaring and firing an event is easy and natural. We'll come back to events later on when they come up naturally in the flow of our discussion. But for now, know that properties and events, which are pretty central to component-based programming, are supported directly by the .NET Framework.

Note that we've already seen how to handle .NET Framework events in all of our ASP.NET applications. Each of the button click handlers uses the standard .NET Framework event mechanism. Again, we'll be discussing events in great depth in a later column.

Types and enumerations: nested classes and enums

You can also have nested classes and other types as members of a type—a class within a class, for instance.

And, nested or not, you can also have a special kind of type called an enumeration, or enum (Enum in Visual Basic .NET). An enum is a type that contains static constant fields with particular integer values. The fields are symbolic names for numeric values and bit flags.

Take a look at this C# code that shows nested classes and enums (you can see this code):

// compile with: csc InnerOuterCS.cs
using System;
class Outer {
   public class Nested {
   // if private, couldn't be accessed outside of Outer
      int value;
      public Nested(int value) {
         this.value = value;
      }
      public void Foo() {
         Console.WriteLine("Outer.Nested.Foo: value is {0}", value);
      }
   }
   // The enum could appear outside of a class, too
   public enum ShortDays { Sun = 0, Mon, Tue, Wed, Thu, Fri, Sat };
}
[Flags] public enum BitFlags { Bit0 = 1, Bit1 = 2, Bit2 = 4, Bit3 = 8 };
class TestOuter {
   public static void Main() {
      Outer.Nested n = new Outer.Nested(5);   // create nested object
      n.Foo();                        // call method
      
      // if the enum was outside a class, it'd be ShortDays.Mon, etc.
      Outer.ShortDays monday = Outer.ShortDays.Mon;
      // can write day *AND* short name of day
      Console.WriteLine("Monday is day #{0}, name is {1}", 
         (int)monday, monday.ToString());

      // get integer for today's day: Sunday = 0, Monday = 1, etc.
      int dayNum = (int)DateTime.Today.DayOfWeek; // system property
      // now convert to our ShortDays type
      Outer.ShortDays shortToday = (Outer.ShortDays)dayNum;
      Console.WriteLine("Today is day #{0}, name is {1}", 
         (int)shortToday, shortToday.ToString());

      // Works with Framework's enum DayOfWeek, too
      DayOfWeek longToday = DateTime.Today.DayOfWeek;
      Console.WriteLine("Today is day #{0}, name is {1}", 
         (int)longToday, longToday.ToString());

      BitFlags b = BitFlags.Bit0 | BitFlags.Bit2;
      Console.WriteLine("b's value is {0}, flags are {1}", 
(int)b, b.ToString());
      Console.ReadLine();
   }
}

And here's the Visual Basic .NET code for the same thing (somewhat longer because each Enum declaration takes one line per value declared.) (You can see this code):

' compile with: vbc InnerOuterVB.vb
Imports System
Class Outer
    Public Class Nested
        ' if private, couldn't be accessed outside of Outer
        Dim value As Integer
        Public Sub New(ByVal value As Integer)
            Me.value = value
        End Sub
        Public Sub Foo()
            Console.WriteLine("Outer.Nested.Foo: value is {0}", value)
        End Sub
    End Class
    ' The enum could appear outside of a class, too
    Public Enum ShortDays
        Sun = 0
        Mon
        Tue
        Wed
        Thu
        Fri
        Sat
    End Enum
End Class

<Flags()> Public Enum BitFlags
    Bit0 = 1
    Bit1 = 2
    Bit2 = 4
    Bit3 = 8
End Enum

Class TestOuter
    Public Shared Sub Main()
        Dim n As New Outer.Nested(5)    ' create nested object
        n.Foo()                         ' call method

        ' if the enum was outside a class, it'd be ShortDays.Mon, etc.
        Dim monday As Outer.ShortDays = Outer.ShortDays.Mon
        ' can write day *AND* short name of day
        Console.WriteLine("Monday is day #{0}, name is {1}", _
         CType(monday, Integer), monday.ToString())

        ' get integer for today's day: Sunday = 0, Monday = 1, etc.
        Dim dayNum As Integer = CType(DateTime.Today.DayOfWeek, Integer)
        ' system property
        ' now convert to our ShortDays type
        Dim shortToday As Outer.ShortDays = CType(dayNum, _
            Outer.ShortDays)
        Console.WriteLine("Today is day #{0}, name is {1}", _
         CType(shortToday, Integer), shortToday.ToString())

        ' Works with Framework's enum DayOfWeek, too
        Dim longToday As DayOfWeek = DateTime.Today.DayOfWeek
        Console.WriteLine("Today is day #{0}, name is {1}", _
           CType(longToday, Integer), longToday.ToString())

        Dim b As BitFlags = BitFlags.Bit0 Or BitFlags.Bit2
        Console.WriteLine("b's value is {0}, flags are {1}", _
            CType(b, Integer), b.ToString())
        Console.ReadLine()
    End Sub
End Class

In both programs, we've created a class called Outer and one called Outer.Nested. These nested classes can be handy for implementing helper objects that are mainly (or exclusively) used inside the outer class. In this case, we made the nested class public so it could be used outside the outer class as well as inside it.

We also created an enumerated type called Outer.ShortDays, with seven values, 0-6, for the days of the week. This could have been created outside the class—if we did, its name would just be ShortDays.

In Main, we created a variable of that type called monday, initializing it with the constant value Outer.ShortDays.Mon. Then we printed its numeric value (by casting it to int/Integer and we printed its name.

We can also get a string that contains its name by using the Format method or convert from a string containing the name to the enumerator's integer value by using Parse. We can even get all of the values and names with GetValues and GetNames. (Enumerators in many systems don't have the ability to tell you their names. Since in the .NET Framework you have metadata for each type, it's possible to work with the names as well as the values.)

You'll notice that you have to cast (using CType in Visual Basic) to convert from an enum type to an integer type. Normally, you don't want to convert to and from integers: we're doing it here ONLY to demonstrate that you can.

Finally, we've created an enum that contains a set of bit flags called BitFlags. The [Flags]/<Flags()> attribute before the declaration is what makes C# and Visual Basic .NET treat this type differently—for instance, the "or" operator is not normally allowed for enums. You can see how we can do bit-wise arithmetic with them and even get the values in a string.

The ASP.NET code is very similar, and this time we don't need session state.

You can run this application.

The only change to the code for the nested class and enums is to change the function that called Console.WriteLine to return a string instead using String.Format.

     Public Function Foo() As String
         Return String.Format("Outer.Nested.Foo: value is {0}", value)
     End Function

We changed the Sub to a Function that returns a string, and used the static/Shared String.Format method to format the output. Note that in many (if not all) cases, String.Format can be called with EXACTLY the same parameters as Console.WriteLine, so modifying the code is extremely easy!

We then wrote a form with four buttons and four labels that correspond to the first four parts of Main that produced output, and put each part of the output in the appropriate button's click handler method, again changing Console.WriteLine statements to call String.Format instead and to set the appropriate label's Text property to the resulting formatted string. (You can see the entire form code.)

We also added some text boxes to accept input for converting between ShortDay enum values and names, and a text box and a check box list to accept input to convert between the decimal value, the bits, and the BitFlags enum so we could print its value.

Here's the code for the event handlers so you can see (you can see the entire code):

Private Sub Button1_Click(ByVal sender As System.Object, 
            ByVal e As System.EventArgs) Handles Button1.Click
    Dim n As New Outer.Nested(5)    ' create nested object
    Label1.Text = n.Foo()                         ' call method
End Sub
Private Sub Button2_Click(ByVal sender As System.Object, 
            ByVal e As System.EventArgs) Handles Button2.Click
    ' if the enum was outside a class, it'd be ShortDays.Mon, etc.
    Dim monday As Outer.ShortDays = Outer.ShortDays.Mon
    ' can write day *AND* short name of day
    Label2.Text = String.Format("Monday is day #{0}, name is {1}", _
        CType(monday, Integer), monday.ToString())
End Sub
Private Sub Button3_Click(ByVal sender As System.Object, 
            ByVal e As System.EventArgs) Handles Button3.Click
    ' get integer for today's day: Sunday = 0, Monday = 1, etc.
    Dim dayNum As Integer = CType(DateTime.Today.DayOfWeek, Integer)
    ' system property
    ' now convert to our ShortDays type
    Dim shortToday As Outer.ShortDays = CType(dayNum, Outer.ShortDays)
    Label3.Text = String.Format("Today is day #{0}, name is {1}", _
        CType(shortToday, Integer), shortToday.ToString())
End Sub
Private Sub Button4_Click(ByVal sender As System.Object, 
            ByVal e As System.EventArgs) Handles Button4.Click
    ' Works with Framework's enum DayOfWeek, too
    Dim longToday As DayOfWeek = DateTime.Today.DayOfWeek
    Label4.Text = String.Format("Today is day #{0}, name is {1}", _
       CType(longToday, Integer), longToday.ToString())
End Sub
Private Sub TextBox1_TextChanged(ByVal sender As System.Object, 
            ByVal e As System.EventArgs) Handles TextBox1.TextChanged
    Dim dayString As String = TextBox1.Text
    Try
        Dim day As Outer.ShortDays = _
            System.Enum.Parse(GetType(Outer.ShortDays), dayString, True)
        TextBox2.Text = CType(day, Integer)
    Catch
        TextBox2.Text = "Invalid Short Name"
    End Try
End Sub
Private Sub TextBox2_TextChanged(ByVal sender As System.Object, 
            ByVal e As System.EventArgs) Handles TextBox2.TextChanged
    Dim dayNum As Integer = Val(TextBox2.Text)
    If System.Enum.IsDefined(GetType(Outer.ShortDays), dayNum) Then
        Dim day As Outer.ShortDays = CType(dayNum, Outer.ShortDays)
        TextBox1.Text = day.ToString()
    Else
        TextBox1.Text = "Invalid Enum value"
    End If
End Sub
Private Sub TextBox3_TextChanged(ByVal sender As System.Object, _
        ByVal e As System.EventArgs) Handles TextBox3.TextChanged
    Dim bits As Integer = Val(TextBox3.Text)
    If bits >= 0 And bits <= 15 Then
        Dim b As BitFlags = CType(bits, BitFlags)
        Label5.Text = "'" + b.ToString() + "'"
        ' checkbox indices are reverse order of bits :(
        CheckBoxList1.Items(3).Selected = b And BitFlags.Bit0
        CheckBoxList1.Items(2).Selected = b And BitFlags.Bit1
        CheckBoxList1.Items(1).Selected = b And BitFlags.Bit2
        CheckBoxList1.Items(0).Selected = b And BitFlags.Bit3
    Else
        Label5.Text = "Invalid value"
        Dim i As Integer
        For i = 0 To 3
            CheckBoxList1.Items(i).Selected = False
        Next
    End If
End Sub

Private Sub CheckBoxList1_SelectedIndexChanged(ByVal sender As _
        System.Object, ByVal e As System.EventArgs) _
        Handles CheckBoxList1.SelectedIndexChanged
    Dim i As Integer
    Dim bits As Integer = 0
    For i = 0 To 3
        bits = bits * 2 ' right shift one bit
        bits = bits - CheckBoxList1.Items(i).Selected ' converts to -1/0
    Next
    TextBox3.Text = bits
    Dim b As BitFlags = CType(bits, BitFlags)
    Label5.Text = "'" + b.ToString() + "'"
End Sub

Some notes: the conversion to and from ShortDays is handled in the two TextChanged handlers for the two TextBoxes. We added a button to the form, but there's no handler for it—clicking it merely causes the form to be submitted and does nothing else.

The various Changed events will be fired on ANY form submission if the text/check boxes have changed, so you can click any button to fire those events. Or do as we've done here—just set the AutoPostBack property of the text boxes and the check box list, and after you change the text box, the form will be posted automatically as soon as you tab away from it, or the check box as soon as you click it. But note that this will fail if the user's browser doesn't have scripting enabled—that's why we have the button at the bottom.

The conversion from a string to an enum (in TextBox1_TextChanged) is a little tricky. We do the conversion by calling the shared/static Parse method in System.Enum. But this method throws an exception if the parsing fails, so we have to catch that exception and handle it. Notice that this version of Parse is case-insensitive if the last parameter is True. Note that we use the Visual Basic .NET Val function to convert to string from integer. Val is handy because, unlike CInt, it doesn't throw an exception if the converstion fails—it just returns zero.

The conversion the other way is simpler because of the System.Enum.IsDefined method, which tells us if the enum value is defined or not. But this doesn't work for combinations of bit flags, so we tested for a range when converting to a BitFlags enum in TextBox3_TextChanged. Note also that we use the And operator to check to see if a particular bit in the enum is set. We can only do this if the enum has the Flags attribute, as we do here.

C# special members (indexers and overloaded operators—NEW!!)

Although they're not directly supported by the .NET Framework, the C# language supports two other types of members: indexers and overloaded operators. Some .NET languages will support these directly, others won't. Visual Basic .NET allows you to use (somewhat awkwardly) C# code that contains indexers and overloaded operators but not to write your own.

Indexers allow you to subscript your object—they're like an overloaded array subscript operator in C++. They become a property called Item in the code that's output, along with special attributes in the metadata. This allows them to be used by languages that don't support indexers directly. We'll talk about these in a future column.

C# also supports C++-like overloaded operators. These become methods with special names, such as op_Addition, in the code that's output—again, along with some special attributes.

Remember that you can always use ILDASM to see what the compiler has generated for you. Just load up the .exe and press Ctrl+M.

Note that languages that don't support these features can, in general, still use them because the code they generate is standard. By the same token, other languages could have other non-standard constructs.

Specifying accessibility of members

We've already seen that you can specify that members are private/Private, accessible only to the class in which they're declared; or public/Public, accessible to anyone. There are several other access levels you can specify, as well.

Before we go on, you'll have to learn (or recall from last time) what an assembly is. An assembly is the smallest unit that can be versioned and installed in the .NET Framework—it consists of one or more files. In our simple examples, our assemblies consist of exactly one .exe file. Each assembly has a manifest in one of the files: You can read this using ILDASM.

If you use the keyword protected/Protected, access will be restricted to the current class (like private) plus all classes that inherit from your class, no matter what assembly they reside in. The .NET Framework calls this type of accessibility "Family."

If you use the C# keyword internal or the Visual Basic .NET keyword Friend, access will be granted to all classes in the assembly you reside in, regardless of inheritance. The .NET Framework calls this type of accessibility "Assembly." (Note: The Visual Basic .NET usage of the keyword Friend is nothing like the C++ keyword friend.)

In C#, you can specify protected internal accessibility (Protected Friend in Visual Basic .NET), which means protected (Family) or internal (Assembly)—in other words, any class in the assembly or inherited from this class (regardless of assembly) can access the member. The .NET Framework calls this "Family or Assembly."

The .NET Framework also supports "Family and Assembly" accessibility (where the accessing class must be a derived class in the same assembly), but there's no way in C# or Visual Basic .NET to express this.

By the way, you can also make the class public/Public, which means that it can be accessed from any assembly, not just the assembly in which it's defined. (The default access for classes is internal/Friend.)

Value Types

Most of the classes we've talked about so far are reference types, meaning they can only be created on the garbage-collected heap using the new/New operator and that the variable itself is a reference to the object, not the actual object. When you assign a reference type, you copy the reference, not the object to which it points, so you can have more than one reference referring to the same object. For instance, StringBuilder is a reference type:

   using System.Text;
   // ....
   StringBuilder a, b; 
   // used StringBuilder because String is immutable
   a = new StringBuilder("Foo");
   b = a;          // a and b refer to same String
   b.Append('d');   // slightly more efficient to use char, not "d"
   Console.WriteLine("a: {0}, b: {1}", a, b); // both refer to "Food"

...or, in Visual Basic .NET:

   Imports System.Text
   ' ....
   Dim a, b as StringBuilder 
   ' used StringBuilder because String is immutable
   a = New StringBuilder("Foo")
   b = a          ' a and b refer to same String
   b.Append("d"C)    ' slightly more efficient to use char, not string
   Console.WriteLine("a: {0}, b: {1}", a, b) ' both refer to "Food"

Both a and b refer to the same object, so when we change b, a changes as well. You can see this in the following diagram:

The semantics of the built-in types, such as int/Integer, are different. They're created on the stack, so they automatically go away when the method you're executing ends. Thus, they do not require dynamic memory allocation nor garbage collection. And when you assign them, you copy the value—there is no reference to them. All of the built-in types are value types, except object/Object, string/String, and the array types.

The .NET Framework also allows you to create your own value types—in C#, you call them a struct (Structure in Visual Basic .NET) rather than a class/Class. Value types are implicitly derived from System.ValueType (except for enumerations, which are implicitly derived from System.Enum) and cannot be derived from any other type. Nor can any other type be derived from them—value types are sealed.

Value types have limited use, but they can be very helpful in making your programs more efficient by reducing memory allocations. You should declare a value type when the type acts like a primitive type, you want to have value semantics, and the data is small (say, under 16 bytes). Large value types are a bad idea because they're passed by value to methods by default—if they're large, they can take a significant amount of time to copy for each method call.

Despite their limitations, value types are extremely handy for writing types such as points, sizes, and complex numbers. For instance, a Complex type might be defined something like this in C#:

   public struct Complex {    // struct, not class, for value types
      double real, imaginary; // private!
      public double Real {
         get {
            return real;
         }
         set {
            real = value;
         }
      }
      // ...and so on...
   }

...and something like this in Visual Basic .NET:

Public Structure Complex ' Structure, not Class, for value types
    Dim realValue, imaginaryValue As Double ' private!
    Public Property Real() As Double
        Get
            Return Real
        End Get
        Set(ByVal Value As Double)
            Real = Value
        End Set
    End Property
    ' ...and so on...
End Structure

You create value types the same way you do any other type: with the new/New operator. (You can also declare them without initialization, as in "Complex a;" or "Dim a as Complex", in which case all the fields will be initialized to zero.) The main difference is that the value type will be allocated on the stack, not on the garbage-collected heap.

Boxing and unboxing

One of the best features of the .NET runtime is that any type, including value types, can be converted to an object/Object. Reference types are all derived from Object anyway, so the conversion is the normal implicit conversion to an object's base type.

But the conversion of a value type requires some work. What happens is that the compiler boxes the value type into an equivalent reference type by creating the boxed type on the heap and copying the value into it. The boxed type is derived from System.ValueType.

This reference type can then be converted to Object and used any place an Object can be used—in data collections, as a parameter to Console.WriteLine—anywhere! That's why, in most .NET languages, you can write code like "object o = 5;" or "Dim o as Object = 5". The result is that o refers to a boxed int/Integer object that contains 5.

Here's a diagram of a boxed object:

So, when we pass value types to Console.WriteLine, as in "Console.WriteLine("a: {0}", a)", what's really happening is that a is converted to an Object by boxing it, and the reference to the boxed object is passed to Console.WriteLine, which calls the Format or ToString method on it.

The runtime also supports unboxing: converting the boxed value type back into an unboxed value type. Doing this requires a cast, as in "int i = (int)o;" or "Dim i as Integer = CType(o, Integer)". When un-boxing in languages such as C#, the data is copied out of the boxed object to the memory for the value type. (In Managed C++, it's not necessary to copy the data.)

Note that boxing creates a separate copy of the value type data. This could be surprising, especially since boxing is automatic. Also, you want to avoid boxing and un-boxing operations where possible—they're extra work that will slow your programs down.

If you're ever wondering what your programs are doing, just examine the IL code with ILDASM. You'll notice that there are BOX and UNBOX instructions for these operations. While the BOX instruction always creates a new object on the heap, the UNBOX instruction merely returns the address of the unboxed portion of the boxed object. Managed C++ can use this address directly; C# can't, and therefore always copies the unboxed portion to a separate memory area.

Special Types

Built-in types

There is a group of types that are special because they're built into the .NET Framework. Most of these are value types. Most languages have different names for these types. For this discussion, we'll use the names of the structs in the System namespace. So, bool/Boolean (below) is really System.Boolean, and so forth.

The simplest is bool/Boolean. It can represent the values true/True or false/False. In C#, the conditions in if statements and loops must be of type Boolean, which prevents the famous error:

   int a; 
   if (a = 5) // ASSIGNMENT, not comparison; error in C# due to type

because the type of a is not bool. (Visual Basic .NET has never had this particular problem.)

There's also a character type, char/Char, which holds a single 16-bit Unicode character, and a string type, string/String, which holds a string of Unicode characters. The string type is a reference type.

There is a set of integer types, signed and unsigned, ranging in size from 8 bits to 64 bits. The signed types are sbyte/SByte, short/Short/Int16, int/Integer/Int32, and long/Long/Int64. Note that Visual Basic .NET doesn't have a signed byte (SByte) type, and that signed byte is not a Common Language Specification (CLS)-compliant type.

The .NET Framework also has an integer type of the computer's native word size called System.IntPtr.

The unsigned integer types are byte/Byte, ushort/UInt16, uint/UInt32, ulong/UInt64, and UIntPtr. Of these, Visual Basic .NET supports only Byte. None of these except Byte are CLS-compliant types, so they can't be used for cross-language programming.

There are two floating-point types, float/Single and double/Double, representing 32- and 64-bit binary floating point numbers. And decimal/Decimal is a type that represents decimal fractions exactly (without rounding error), so numbers such as 0.1 and 0.01, which cannot be represented exactly in binary floating point (just as 1/3 cannot be represented exactly in decimal—it's the repeating decimal 0.33333...), can be represented exactly. This gives you more accurate representation and math when working with currency.

And, of course, object/Object is a built-in reference type.

Language support for built-in types

Some of these types have built-in support in various languages. For instance, the C# language includes literals for bool (true and false), characters, strings, the integer types (in decimal or hex, but not octal; suffixed with u or U for unsigned and/or l or L for 64-bit), and the floating-point types (suffixed with f or F for float/Single; or m or M for decimal/Decimal). Visual Basic .NET also includes literals for all of its supported built-in types, such as appending "C" to single-character strings to make them Char, "D" to numbers to make them Decimal, "R" to numbers to make them Double, or "I" to make a number Integer.

Delegates

A delegate is an object that contains a reference to a method and, if the method is an instance method, to a particular object. You can make a delegate refer to any appropriate method, and you can call the method through the delegate at any time. So, you see, delegates serve the same function as a function pointer in C or C++; however, they are type-safe and secure. They are commonly used for callbacks and for events.

To declare a delegate, you describe the parameters and return type of the type of method the delegate will be able to call, as in the following C# code:

public delegate void MyDelegateType(int a);

And in Visual Basic .NET, the above delegate declaration would look like this:

Public Delegate Sub MyDelegateType(ByVal a As Integer)

This declares MyDelegateType as a delegate that can call a method that takes an int/Integer and returns nothing. This is like doing a typedef of a function pointer type in C or C++.

Then you declare a delegate variable—this is like declaring and initializing the actual function pointer in C or C++. Here's the C# code for this:

   // for an instance method
MyType o = new MyType();
   MyDelegateType MyDelegate = new MyDelegateType(o.MyMethod);
   // ...or, for a static method:
   MyDelegateType MyDelegate = 
new MyDelegateType(MyType.MyStaticMethod);

And here's the VB .NET code:

' for an instance method:
Dim o As New MyType()
Dim MyDelegate As New MyDelegateType(AddressOf o.MyMethod)
' ...or, for a Shared method:
Dim MyDelegate2 As _
New MyDelegateType(AddressOf MyType.SharedMethod)

This declares a variable MyDelegate that refers to MyObject.MyMethod(int), which returns nothing. If there is no overload of MyMethod that matches the return type and parameter list of the MyDelegate type, you will get an error.

It also declares a second delegate, MyDelegate2, which refers to a static/Shared method that takes an int/Integer and returns nothing.

When you make a call using the delegate, it's as if you're calling the method on the object you specified:

   MyDelegate(5)

It's really that simple. And the power of it is that MyDelegate can refer to (and call) any method in any object (or any static method) that takes an int/Integer and returns nothing.

Namespaces

We've mentioned namespaces before, mainly in the context of the using/Imports statement. All a set of using/Imports statements does is say to the compiler, "if you can't find an identifier in the current namespace, search each of the namespaces specified in these using/Imports statements."

So, for instance, we've been making extensive use of the Console class. The using System;/Imports System statement tells the compiler to try Console alone first, and then to prepend System. It's only if Console isn't found in any of the namespaces that the compiler reports an undeclared identifier.

So, namespaces are just shorthand for naming classes, and using/Imports is shorthand for referring to classes; nothing more, nothing less.

You can declare your own namespaces. Just enclose your program in a namespace/Namespace block as in this C# example:

   namespace DrGUI.FirstNamespace {
      // class and other declarations go here
      class Foo {  // actually DrGUI.FirstNameSpace.Foo
         // ...
      }
   }

...or as in this Visual Basic .NET example:

Namespace DrGUI.FirstNamespace
    ' class and other declarations go here
    Class Foo ' actually DrGUI.FirstNameSpace.Foo
        ' ...
    End Class
End Namespace

These namespaces can be nested. Note when you use a namespace, you're changing the names of all the types you declare within that namespace.

In order to avoid namespace collisions, the .NET Framework recommends that you name your namespaces with your company name and a technology or project name, such as Microsoft.Win32.

We'll skip using namespaces for the simple examples we're doing here. But for larger programs, they're very handy indeed.

Namespaces are not the same as assemblies

The name of a particular thing and the assembly it lives in are two different things. Namespaces are about creating and using long names of various types. The assembly it lives in is what .exe or DLL it's found in. These are two entirely separate things.

So, you can have more than one namespace in a given assembly, and the types in a particular namespace could be spread over multiple assemblies. Namespaces and assemblies aren't related at all—they're completely orthogonal.

Give It a Shot!

If you've got .NET, the way to learn it is to try it out ... and if not, please consider getting it. If you spend an hour or so a week with Dr. GUI .NET, you'll be an expert in the .NET Framework before you know it.

Be the First on Your Block—and Invite Some Friends!

It's always good to be the first to learn a new technology, but even more fun to do it with some friends! For more fun, organize a group of friends to learn .NET together! If you don't have any friends, make some—perhaps by visiting the message board. (But remember that the message board is for people who DO have friends, too.)

Some Things to Try...

First, try out the code shown here. Some of it is excerpted from larger programs; you'll have to build up the program around those snippets. That's good practice.

Next, try out some of the new features of the .NET Framework. Give properties a try—they're very cool. Try doing read-only and write-only properties. You can even try using delegates like function pointers—they're pretty simple, flexible, and cool. Try some enumerations including flags—and maybe even a nested class or two.

Try a value type, but be sure to read the documentation for the restrictions on value types before you bump your head up against them. For more extra credit, use overloaded operators in C# in your value type.

Next, declare a class or two in a namespace and check the metadata to see what changed. Construct proper using/Imports statements to access your classes from other namespaces.

If you want to dig into the documentation some to find out how to make programs that span multiple modules and assemblies, try creating a multi-assembly program and try out the various protection modifiers.

What We've Done; What's Next

This time, we talked about the basics of the .NET Framework type system and showed a bit of how the .NET Framework implements them.

Next time, we'll discuss inheritance, interfaces, polymorphism, and overriding methods.

Show:
© 2016 Microsoft