To help you understand .NET development from a Visual FoxPro perspective, this article introduces you to the concept of enums and shows you how to use them to improve code quality.
Enums are a useful concept that greatly facilitate coding and therefore code quality by making a set of potential values easy to use and memorize and, at the same time, protect against the definition of invalid values. Enums have been used for a while in Visual Basic 6 and .NET languages, and in technologies such as COM. Enums don't exist in Visual FoxPro, however, making this a potential stumbling block for VFP developers who want to include .NET in their toolbox.
Imagine writing an algorithm that can send a piece of collateral (such as an invoice) to a customer. The algorithm is supposed to support a number of different delivery methods for the invoice, such as e-mail, snail mail, and fax. That algorithm might be implemented as a method that takes three parameters including Customer ID, invoice details, and delivery method. In Visual FoxPro, you could define such a method like this:
PUBLIC FUNCTION SendInvoice(CustomerID, InvoiceDetail, DeliveryMethod)
* Implementation goes here...
ENDFUNC
What would you pass as the third parameter? (Although the first two parameters are important, they aren't of interest in the context of this article.)
One option would be to pass a text string such as "fax." However, this is error prone due to issues such as case sensitivity, leading and trailing spaces, required memory allocation, and localization problems. For this reason, this is typically handled through pre-determined code values. For example, e-mail would be 1, snail mail would be 2, and fax would be 3. Using this approach, you could start an implementation of this method like this:
FUNCTION SendInvoice(CustomerID, InvoiceDetail, DeliveryMethod)
DO CASE
CASE DeliveryMethod = 1
* Send email
CASE DeliveryMethod = 2
* Print and send snail mail
CASE DeliveryMethod = 3
* Send fax
ENDCASE
ENDFUNC
Subsequently, you could call the method or function using this line to send an invoice by fax:
SendInvoice(ID,Detail,3)
However, there are a few issues with this approach. It isn't intuitive. The number 3 was picked arbitrarily and has no particular correlation with sending faxes. Also, IntelliSense provides no indication in the editor about the value that's expected and the values that are valid (at least not without some extra trickery). To make things a little easier, languages such as Visual FoxPro (and also C/C++) use a concept called "defines." Using a define, you can assign plain-text names to arbitrary values. You can significantly simplify the above scenario through the following defines:
#DEFINE EMAIL 1
#DEFINE SNAILMAIL 2
#DEFINE FAX 3
To create this definition as generically as possible, you can store this in a separate file (typically with a .h extension) which you can then include in any number of source files that intend to call your method. Using this technique, you can now call the method like this (assuming the file name of the include file is "Definitions.h"):
#INCLUDE Definitions.h
SendInvoice(ID,Detail,FAX)
Similarily, you can change your method like this:
#INCLUDE Definitions.h
FUNCTION SendInvoice(CustomerID, InvoiceDetail, DeliveryMethod)
DO CASE
CASE DeliveryMethod = EMAIL
* Send email
CASE DeliveryMethod = SNAILMAIL
* Print and send snail mail
CASE DeliveryMethod = FAX
* Send fax
ENDCASE
ENDFUNC
This makes your method more readable and maintainable. Note, however, this isn't required, because to the compiler, FAX and 3 are identical. Therefore, you can mix and match the approaches you want to take. For instance, it's still possible to call your method like this:
SendInvoice(ID,Detail,3)
Define statements and include files make it somewhat easier to use your method. However, things are far from perfect. For one, the developer using your method has to have the include file to take advantage of this approach. If you've ever tried to use COM components or automate Microsoft Word, you'll be familiar with this problem. Many components have tens of thousands of values you can pass, yet the defining include files aren't available. (To make matters worse, the documentation generally refers to the defined names, rather than individual values). And, even if the .h file is available, such files are often confusing because they may contain thousands of constant definitions that are non-intuitive and hard to browse and manage. (For an example, check out the FoxPro.h file).
Also, the compiler can't currently protect against the incorrect use of your method. The following lines would be perfectly valid as far as the compiler is concerned. However, during runtime this would create all kinds of problems:
SendInvoice(ID,Detail,0)
SendInvoice(ID,Detail,4)
Another issue is the fragility of this technique. In theory, you should be able to change underlying values when you need to. For example, you could change your include file to the following:
#DEFINE EMAIL 0
#DEFINE SNAILMAIL 1
#DEFINE FAX 2
All your values have now changed, but as long as everyone used FAX rather than 3, applications should still compile correctly. The problem is there's no guarantee people used the word instead of the number and there's no automatic way to check this. Therefore, changing pre-compiler defines is a rather hairy undertaking which you should avoid when possible.
Another issue is that the code editor and IntelliSense are of no particular help in this case (short of going to extraordinary lengths in editor customization, which wouldn't be tied to the class or method and would most likely only work on your machine).
Enums to the rescue
Enums tackle exactly this problem. Similar to "defines," enums let you define a number of valid options. The big difference between enums and defines is that you use enums to create a new data type, while defines are a simple pre-compiler text-replace trick (note that constants are still available in .NET where they are useful for slightly different scenarios). Enums are special classes the .NET runtime and editors can use to figure out what's valid and what's represented. If you were to implement the above example in VB.NET, you could first define all applicable values in a special Enum construct:
Public Enum DeliveryMethodEnum
Email
SnailMail
Fax
End Enum
Not surprisingly, C# provides an almost identical language construct:
public enum DeliveryMethodEnum
{
Email,
SnailMail,
Fax
}
Now that you defined the new enum, you can use it in your method. Here's the VB.NET version:
Public Sub SendInvoice( _
ByVal CustomerID As Object, _
ByVal InvoiceDetail As Object, _
ByVal DeliveryMethod As DeliveryMethodEnum)
Select Case DeliveryMethod
Case DeliveryMethodEnum.Email
' Send email
Case DeliveryMethodEnum.SnailMail
' Print and send snail mail
Case DeliveryMethodEnum.Fax
' Send fax
End Select
End Sub
Here's the C# version:
public void SendInvoice(object CustomerID,
object InvoiceDetail, DeliveryMethodEnum DeliveryMethod)
{
switch (DeliveryMethod)
{
case DeliveryMethodEnum.Email:
// Send email
break;
case DeliveryMethodEnum.SnailMail:
// Print and send snail mail
break;
case DeliveryMethodEnum.Fax:
// Send fax
break;
}
}
You can now use this method like this:
o.SendInvoice("x","y",DeliveryMethodEnum.Fax)
At first, this may not look very different from #Define statements, but the differences under the hood are enormous and surface quickly. For one, the enum is compiled into an assembly (generally the same assembly defining the method). This means the enum and its values are automatically available whenever the method is accessible. Scenarios in which third-party components (such as Microsoft Word) define a huge number of possible code values that aren't accessible due to the lack of an include file can't happen with enums.
Also, the editor is immediately helpful when you try to call the method. (Figure 1 shows the Visual Basic .NET IntelliSense in action.) But not just the editor, the compiler also benefits from information available in enums. The compiler can verify all the potential values during compilation. If you were to pass an invalid value (such as DeliveryMethodEnum.InstantMessage), the code would fail to compile, preventing accidental incorrect use of the method.
No matter how many different enums you define, each enum only provides a limited number of options that are significant for each individual scenario. In this example, three valid settings apply. In the header (.h) file example above, these three settings may be defined with thousands of other settings that apply to other methods, so there would be no easy way to determine which ones apply to the current scenario.
Enum details
Under the hood, enums are still numeric values. If you investigate the IL code generated by the compiler, you'd see your enum is compiled with three integer values (0, 1, and 2 respectively). By default, the compiler assumes sequentially numbered integer values, starting with 0. However, you can influence the numbers used internally. If you want your enum to be "1-based," you could define it like this:
Public Enum DeliveryMethodEnum
Email = 1
SnailMail = 2
Fax = 3
End Enum
Under normal circumstances, you probably wouldn't worry about this detail, but there are scenarios in which this functionality can be useful. For example, you may want to compile your first Visual FoxPro example into a COM component and call it from .NET. In this case, you'd either have to pass integer values to the VFP method, or you could make your life easier and define an enum in .NET -- just for the purpose of calling the COM component. In that case, it would be important to define the values of each item in the enum appropriately.
Values don't have to be sequential. The following enum would be perfectly valid:
Public Enum OlympicCities
Atlanta = 1996
Barcelona = 2000
Athens = 2004
Peking = 2008
End Enum
In most scenarios, individual values of enums are integers, but they can sometimes be floating point numbers:
Public Enum Fractions
OnePointFive = 1.5
TwoPointFive = 2.5
End Enum
It's important to realize that even though enums are stored as numbers, their individual names don't get lost during runtime. This example prints "Athens" into the output window:
Dim City As OlympicCities = OlympicCities.Athens
Console.WriteLine(City.ToString())
However, you can explicitly cast the enum to an integer to see the year assigned to the city:
Dim City As OlympicCities = OlympicCities.Athens
Console.WriteLine(CType(City,Integer).ToString())
The C# version is slightly different:
OlympicCities City = OlympicCities.Athens;
Console.WriteLine(((int)City).ToString());
You can also go the opposite way. For example, you can easily find out what city the 1996 Olympics were in by casting the integer value 1996 to the type "OlympicCities" (in VB.NET):
Dim City As OlympicCities
City = CType(1996, OlympicCities)
Console.WriteLine(City.ToString())
Here's the C# syntax:
OlympicCities City;
City = (OlympicCities)1996;
Console.WriteLine(City.ToString());
This is a slightly tricky operation. If you cast an invalid value (such as 1997), no enum with a matching value would be defined. Therefore, the best the runtime environment can do is set the value of the "City" variable to 1997. This doesn't result in a runtime error because this particular enum internally is an integer, and so is the value 1997. However, it makes little sense in the example and may lead to unexpected errors down the road. Keep in mind that whenever you perform type casts, you're directly responsible for making sure the cast succeeds and makes sense. This is a prime example of the dangers of manual casting.
To make this a safe operation, first check whether the value you're trying to cast is defined in the enum. You can do this using the IsDefined() method on the base enum class. This method requires two parameters. The first parameter is the enum type against which you want to verify the value. The second parameter is the value you want to make sure exists. VB.NET and C# do this in slightly different ways. First, here's the VB.NET version:
If [Enum].IsDefined(GetType(OlympicCities), 1996) Then ...
"Enum" is a keyword in VB.NET, so you have to wrap square brackets around the word Enum when you want to refer to the class named Enum (which is defined in the System namespace). The second mystery here is how you retrieve the first parameter. Use the GetType() method to retrieve a reference to the "OlympicCities" type. This returns a Type object that provides information about the enum you created, letting the GetType() method to inspect the enum and match the second parameter with it.
The C# version follows the same concept, but uses slightly different syntax:
if (Enum.IsDefined(typeof(OlympicCities), 1996) { ... }
Voilá! Now you can verify whether the value is defined (and therefore valid) and perform the cast only in the If statement.
Of course, you may also want to saddle the horse from the other side and start out with the name of the city. This is possible as well. You can use a special Parse() method on the Enum object to convert a string into a valid enum object:
Dim City As OlympicCities
City = [Enum].Parse(GetType(OlympicCities), "Atlanta")
Console.WriteLine(CType(City, Integer).ToString())
Again, you use the Enum object to parse the value based on a specific enum type. By now, you can probably guess the C# version:
OlympicCities City;
City = Enum.Parse(typeof(OlympicCities), "Atlanta");
Console.WriteLine(((int)City).ToString());
In this case, the string you pass has to be defined in the enum. Otherwise, there's nothing useful the parse method can do. If you pass an incorrect value (such as "Houston"), an ArgumentException would be thrown at runtime.
The Enum class also provides a simple way to retrieve a list of all settings defined for an enum. This code lists all the Olympic cities you defined:
Dim Cities As String()
Cities = [Enum].GetNames(GetType(OlympicCities))
For Each City As String In Cities
Console.WriteLine(City)
Next
The C# version is also straightforward:
String[] Cities;
Cities = Enum.GetNames(typeof(OlympicCities));
foreach (string City in Cities)
{
Console.WriteLine(City);
}
After you're familiar with the basic functionality provided by enums, we recommend you investigate the Enum class further. You may be surprised at how much functionality is provided by this class.
A useful development technique
Enums are a great way to increase developer productivity and code quality overnight. They're easy to use and understand and should be incorporated into all .NET development efforts. However, it's also important to realize that enums aren't entirely foolproof because of their underlying reliance on integers and floating point values. However, such problems are extremely rare because they practically have to be introduced intentionally. Also, it's possible to automatically analyze code for potential problem cases, such as casting a number to an enum without first checking the validity of the number in that context. You can do this using a technique called "static analysis," which represents a computerized and automated code review. The most common tool used for static analysis is FxCop, which is provided by Microsoft free of charge (http://www.gotdotnet.com/team/fxcop).
By Claudio Lassala and Markus Egger