Discover how to apply your Visual FoxPro programming skills to working with Visual Studio .NET, including .NET Framework, VB.NET, and Visual C#.
And discover why and when you should use .NET.
As you're probably aware, there's a new tool in town: Visual Studio .NET. At least it's still new to most Visual FoxPro developers, who tend to stick with their development tool of choice and don't venture too far beyond the core tool, or at farthest, SQL Server. "Why develop in Visual Basic, when it can't even do simple inheritance?" many of us have argued. Now, however, things are different: Visual Studio .NET and its core languages, C# and VB.NET, have an object model that's very similar to the one in Visual FoxPro. This presents an excellent opportunity for VFP developers to extend their reach and add more options to their toolbox.
Now, before people get too excited, let's set one thing straight from the beginning: We don't suggest you abandon Visual FoxPro for Visual Studio .NET! Visual FoxPro is an excellent tool for many things, in particular building database applications in stand-alone environments, which accounts for a large percentage of business applications. If you've used Visual FoxPro to successfully build these types of applications in the past, then there's no reason to abandon the tool.
However, many VFP developers are curious about Visual Studio .NET, and due to the much broader focus of VS.NET, there are many things you can do in this environment that you can't -- or only with great difficulty -- achieve with Visual FoxPro. Projects that fall in this category extend from enterprise-size distributed applications, to high performance multi-threaded applications, to Web (service)-based applications, and even mobile device or Small Personal Object Technology (SPOT) device applications. In all these scenarios, an interesting option is the combination of VFP with Visual Studio .NET technology. But, to implement that type of solution, you have to be familiar with the Visual Studio .NET environment and the syntax of its languages.
You may have heard that using Visual Studio .NET generally includes learning a lot of new technology and a steep learning curve. To a certain extent, this is undoubtedly true. Visual Studio .NET represents a massively reworked development environment that takes some getting used to, no matter what language you used before. However, Visual FoxPro developers have a significant advantage: You know object-oriented development. Absolutely everything in Visual Studio .NET is an object. This means it's arguably easier to learn VB.NET as a VFP developer, than it is for VB6 developers. But, of course, there are also differences that VFP developers must be aware of, and that is the purpose of this article.
So, we'll start with some fundamentals.
Everything is an object
Many of us would consider Visual FoxPro a language built on object-oriented principles, but in reality, VFP is a mix of traditional (procedural) and object-oriented development. Consider this code snippet, for instance:
LOCAL loObject
loObject = CreateObject("TestClass")
? loObject.GetCurrentDate()
DEFINE CLASS TestClass AS Custom
FUNCTION GetCurrentDate()
RETURN "The date is: " + Transform(Date())
ENDFUNC
ENDDEFINE
In this example, we use a method on an object, which is based on a class that inherits from VFP's internal "custom" class to get the current date value. At first, this seems like a true object-oriented snippet of code. In reality, however, the code makes heavy use of procedural constructs. We use the Date() function, for example, to retrieve the current date. We also use the Transform() function to convert the date into a string. Clearly, these functions aren't members of objects, and are therefore plain procedural.
In Visual Studio .NET, on the other hand, there's no Date() function. There's no Transform() function either. Instead, there's a DateTime object. This object has a number of properties and methods you can use to retrieve the current date (as well as the current time, and so forth). Here's a simple example that uses C# code and retrieves the current date using the "Today" property of the DateTime object:
DateTime.Today;
Note that there also isn't a "?" command in VS.NET. Instead, there's an object that takes care of that: the "Console" object. So, to "print" the current date, you could use this C# code:
Console.WriteLine(DateTime.Today);
Note that VS.NET applications may not have a "screen" to which to print. In fact, they're more likely not to have a default window to which to print. Depending on the type of VS.NET project you create, this line of code may print the text to a simple DOS-like output window, or it may write to the debug output window.
You may have heard that the core VS.NET languages (C# and VB.NET) are similar in many ways. In fact, the VB.NET equivalent of the line of code above is almost indistinguishable from the C# version:
Console.WriteLine(DateTime.Today)
The only difference is the semi-colon at the end. Of course, this is a somewhat extreme example, but it communicates one of the fundamental truths about developing for the .NET environment: Discussions about which language is better are rather dull! The main reason is that all .NET languages use the same exact objects, and because everything is an object in the Common Language Runtime (CLR), the differences are mainly stylistic, and it all boils down to personal preference (in most cases anyway).
At this point, you may wonder what the equivalent of the Transform() method would be in VS.NET. Well, to answer that question, we should take a step back and take a closer look at the VFP example we provided earlier. What would a method such as Date() return in VFP? Correct! A value of type "Date." Certainly, this value wouldn't be considered an object. It has no methods and properties, and occupies a simple memory address that stores nothing but the binary representation of the date value. Again, in VS.NET, things are a bit different because absolutely everything is an object. Therefore, the value stored in the Today property is an object of the class DateTime. Not surprisingly, this object has properties and methods, and one of these methods is the equivalent of VFP's Transform() method: ToString(). Therefore, you could convert the DateTime object to a string before you print it to the Console:
Console.WriteLine("The date is: " + DateTime.Today.ToString());
This line of code is C#, as you may have noticed because of the semi-colon. If you prefer VB.NET, simply leave the semi-colon out, and you have valid VB.NET code.
The ToString() method is rather handy. We were quite lucky that the DateTime class has that method, or were we? Well, not really. Every class in .NET (no matter whether it's provided for you or whether you create your own class) inherits from the most basic type of an object, which is (intuitively) named "Object." Naturally, this may happen through several levels of inheritance, but no matter what, "Object" is always the root of the inheritance tree. And ToString() is defined based on the Object class. Therefore, every class (which means absolutely everything in VS.NET because everything is an object) features the ToString() method.
So, what exactly is returned to the caller from the ToString() method? A simple data type: a string. But what does that mean? Didn't we say everything in VS.NET was an object? (And the answer is "yes.") The string that's returned by the ToString() method isn't a string variable, but it's a string object. You can use it like you'd use a string variable in Visual FoxPro. For example, you could concatenate strings like this:
String1 = String2 + String3;
On the other hand, you could call methods on the string object like this:
String1 = String2.ToUpper();
This would store an upper-case version of the second string in the first string.Syntax-wise, this is quite nice. You can treat strings as if they were simplistic data types whenever that's handy (as in the concatenation example), then you can use the same string as if it was an object whenever that's more useful. Behind the scenes, the runtime has to do a lot of work to make this happen. This process is called "boxing" or "unboxing." For now, however, we won't discuss these details. Because you don't need to know the details to use it, we'll just ask you to accept that this is how it works.
You can take the above example one step further. Say you want to take the value of one string, assign its upper-case version to another string, then replace some of the text in the string, as in this Visual FoxPro example:
String1 = Upper(String2)
String3 = StrTran(String1,"X","Y")
Or, you could have shortened the process like this:
String1 = StrTran(Upper(String2),"X","Y")
Either way, the Upper() method returns a simple string you can then pass to the StrTran() method. Not surprisingly, Visual Studio .NET accomplishes the same task using method calls on the string object:
String1 = String2.ToUpper();
String3 = String1.Replace("X","Y");
What's interesting (and probably unexpected) is that the first expression, String2.ToUpper(), evaluates to an object you can use right away. In other words, you can consider that whole expression to be an object with methods and properties you can call like you would any other object:
String1 = String2.ToUpper().Replace("X","Y");
Although this may seem a little unusual at first, it makes a lot of sense. Also, on complex stacked expressions, this "left-to-right" approach is much easier to read than VFP's "inside-out" approach (which is also supported by VS.NET but not used frequently).
But not absolutely everything could be an object, right? How about a simple expression such as "x"? Well, it is! The following expression is perfectly valid in VS.NET:
String1 = "x".ToUpper();
In fact, it would be fine to say:
String1 = 25.ToString();
It would also be fine to say:
String1 = (Val1 * 20).ToString().Trim().Replace("1","2");
At this point, it should be clear that objects are important in VS.NET. Without objects, nothing would work. In fact, many of the core concepts of languages (such as logical expressions) are based on objects. For example, Boolean expressions are objects. So, the following lines are perfectly valid:
String1 = true.ToString();
String2 = (v1 == v2).ToString();
This time, the VB.NET version is slightly different:
String1 = True.ToString()
String2 = (v1 = v2).ToString()
The key thing to understand here is that none of the .NET languages can operate without having a set of basic objects available. This set of basic objects is known as the .NET Framework. We should also point out that this set of basic objects includes several thousand classes.
Creating your own classes
Naturally, VS.NET also lets you create your own classes. You've already seen an example of how to do that in Visual FoxPro. Here's the core syntax that defined the VFP class:
DEFINE CLASS TestClass AS Custom
ENDDEFINE
This defines a simple VFP class that inherits from one of the most basic VFP base classes: "custom."
We can easily do something similar in VS.NET. First, here's the VB.NET version:
Public Class TestClass
End Class
As you can see, there are syntactical differences, but the concept is similar and easy for VFP developers to understand. The C# version is a little more obscure:
public class TestClass
{
}
Although this looks a little different, the idea is the same. The main difference is that VFP, as well as VB.NET, have "end keywords" to define the end of the class. C# uses curly brackets to indicate the same thing.
Note that you might encounter slightly more confusing versions in C#, as line-feeds are ignored by the C# compiler. Therefore, the following version is logically identical to the previous one. Don't let that confuse you.
public class TestClass { }
One of the main differences between the VFP and the VS.NET versions is the definition of the parent class. In VFP, the class inherits from the "Custom" class. The .NET classes don't seem to inherit from any classes. This isn't entirely true, however. Remember we told you that everything in VS.NET inherits from the "Object" class. Therefore, if the compiler encounters classes that don't define a parent class, the class "Object" is automatically chosen. You're free to specify the parent class, though. Here's how you do that in VB.NET:
Public Class TestClass
Inherits Object
End Class
The "Inherits" keyword is VB.NET's equivalent to VFP's "as" keyword. Note that Inherits has to be on a new line.
In C#, things are a bit less verbose:
public class TestClass : Object
{
}
Note that the colon (:) replaces VFP's "as" keyword.
Of course, you can build a more sophisticated inheritance tree by defining additional subclasses. In Visual FoxPro, you could do this using these lines:
DEFINE CLASS TestClass2 AS TestClass
ENDDEFINE
At this point, the VB.NET equivalent shouldn't surprise you:
Public Class TestClass2
Inherits TestClass
End Class
Neither should the C# version:
public class TestClass2 : TestClass
{
}
The mechanics of all this are practically identical, no matter whether you use VFP or VS.NET. As you can see, if you're a VFP developer, there isn't much here that's new. If you're a VB6 developer, this is groundbreaking. That's why we told you VFP developers have a head start. Also, there are a number of additional features you can use when defining classes with methods and properties, such as overriding version control, etc. We'll have to wait to discuss this in a future article, though.
Strong typing
There's quite a bit more to understand about classes. One of the areas where VFP developers will see a major difference is strong typing. In Visual FoxPro, everything has a type (string, numeric, date, etc., with "object" being a special case). But, these types are rather weak. In VFP, you can change types on-the-fly, as in this example:
MyName = "Markus"
MyName = MyName + " Egger"
MyName = 1
The compiler won't complain about this; neither will the runtime. In fact, in many VFP programs, this is desired, although we would consider it bad form. Most likely, someone else will assume "MyName" is a string, and use it accordingly, which may lead to hard-to-find bugs. (This is why naming conventions are so important in Visual FoxPro).
The trouble gets even worse when objects come into play. For example, you could create a Form object and display it:
LOCAL oForm
oForm = CreateObject("Form")
oForm.Show()
This works perfectly fine. However, what if you did this:
LOCAL oForm
oForm = CreateObject("Form")
oForm = CreateObject("TestClass")
oForm.Show()
The TestClass object you instantiate (and created above) doesn't have a Show() method. Therefore, this would fail during runtime. The compiler has no way of checking for that ahead of time. This particular example may seem stupid (why would someone ever do that?), but in real-world applications, things are a bit trickier and accidentally assigned object references are a major quality concern.
In VS.NET, everything is an object. But not just any object. Instead, when objects are used, they are of a specific class. For instance, if you were to instantiate an existing class such as a Windows form, you would first create the reference variable and then instantiate the object. Here's the VB.NET version:
Dim oForm as Form
oForm = New Form
The "Dim" keyword is similar to VFP's "local" keyword. Note that the "as", however, is very different from VFP's "as" keyword. In VFP, you could define your reference variable with a type as well:
LOCAL oForm as Object
Or, you could do this:
LOCAL oForm as Form
However, this has no logical implication, and the compiler ignores it. It's simply a feature that lets the editor display the IntelliSense drop-downs. Therefore, you could do this in VFP:
LOCAL oForm As Object
oForm = "Hello World!"
This code would compile and run fine. In .NET, however, this code would fail to compile:
Dim oForm As Form
Dim sTest as String
sTest = "Hello World!"
oForm = sTest
The VB.NET compiler compares the variable type to the value that's assigned to it, realizes the new value is a string and not a Form, and refuses to compile the application. This is a good thing as most likely this isn't intended, unless you wanted to write confusing code.
Note: VB.NET also supports some "weakened" typing. When the compiler option "strict" is set to "off" (as it is by default), VB.NET will perform some simple conversions automatically, if it considers them safe. For instance, you can convert a number into a string automatically. But, you can't assign a string to a form. Note that "option strict off" is dangerous and generally a bad idea because results will sometimes be surprising, and type conversions should never happen by accident. Our recommendation is to turn option strict "on" and never touch it again. (You have to do this for each project.)
Here's the C# version of the earlier code sample:
Form oForm;
oForm = new Form();
It looks a bit different, but it's similar in concept. In C#, the "Dim" and "as" keywords are simply "optimized" away. Instead, you simply define the type ("Form") and the name of the reference variable ("oForm").
Note that both VB.NET and C# support syntax shortcuts for creating reference variables and assigning new values (objects) to them:
Dim oForm As Form = new Form
Or, here's an even shorter version:
Dim oForm As New Form
Here's the C# version:
Form oForm = new Form();
As you can see, this is logically the same, but much easier to type. Therefore, this is the version you'll see practically everywhere. This is also one of our top enhancement requests for VFP. Wouldn't it be nice to use this syntax?
LOCAL MyName = "Markus"
LOCAL oForm = CreateObject("Form")
Unfortunately, this isn't currently supported in VFP.
So far, the strong typing you've seen in VS.NET is straightforward. However, there's more. It's important to realize that Visual Studio .NET doesn't differentiate between "classes" and "types." You can use the two terms interchangeably. Considering that absolutely everything is an object, this makes sense. After all, a "string" data type is an instance of the string class. The opposite is true as well. A form created out of a "Form" class is considered to be "of type Form." That's because, technically, there's no difference between the two objects (at least not as far as we're concerned in this article), even though one is more complex than the other.
This is also true for the new classes you create. The "TestClass" class you created above is considered a type. You would say "I created a new object of type TestClass," or "I'm using my new TestClass type." But, of course, you could still say "I'm using my TestClass class" or even "I'm using a string class," and that would be correct as well.
When you instantiate your new type, create an object reference, like this:
Dim oTest as TestClass
oTest = New TestClass
Here's the example in C#:
TestClass oTest = new TestClass();
What's interesting (and not immediately obvious) is that any subclass derived from "TestClass" would also be a valid instance. This example would be perfectly fine:
TestClass oTest = new TestClass2();
The reason is that TestClass2 is a TestClass. The second class has absolutely everything the first class has, and therefore, the compiler is satisfied that no matter what you do with the oTest reference, it will function as expected. To demonstrate this, add a method to your TestClass type (VB.NET):
Public Class TestClass
Public Function GetDate() As String
Return DateTime.Today.ToString()
End Function
End Class
As you can see, the definition of the method in VB.NET is intuitive. The only difference to a definition of a method in VFP is that you have to define a return type in VS.NET, which isn't surprising considering VS.NET's strongly typed nature.
Here's the C# version:
public class TestClass
{
public string GetDate()
{
return DateTime.Today.ToString();
}
}
This is also intuitive for C++ developers. But, the basic idea is simple. The keyword "Function" is "optimized" away, and the return type is specified before the method name. Again, the end of the method is defined by curly brackets, rather than by an "end xxx" keyword. (Note: We will present a more in-depth discussion of method definitions in a future article.)
Remember you still have your "TestClass2" type, which inherits from "TestClass." Now, assume you instantiate the classes and call the GetDate() method:
TestClass oTest = new TestClass();
oTest.GetDate();
Not surprisingly, this will work fine. But what if you did this:
TestClass oTest = new TestClass2();
oTest.GetDate();
Because TestClass2 is derived from TestClass, it also has the GetDate() method, and everything will work fine. The compiler knows this, and is satisfied that the operation is "type safe." In fact, this will work no matter how complex TestClass or TestClass2 get, because based on the rules of inheritance, TestClass2 will always support everything TestClass supports.
Now, take a closer look at TestClass2 and enhance it by adding another method. Here's the VB.NET version:
Public Class TestClass2
Inherits TestClass
Public Function GetLongDate() As String
Return DateTime.Today.ToLongDateString()
End Function
End Class
Here's the C# version:
public class TestClass2 : TestClass
{
public string GetLongDate()
{
return DateTime.Today.ToLongDateString();
}
}
Assume you want to execute code similar to that above, but call your new method as follows:
TestClass oTest = new TestClass2();
oTest.GetLongDate();
This time the result will be a compiler error. Why? The object you instantiate has a GetLongDate() method you should be able to call, right? Wrong. Although this would work in the current scenario, it wouldn't work all the time. Keep in mind that you could assign any "TestClass" instance to the "oTest" variable, and these objects may or may not have a "GetLongDate()" method. If that was the case, the application would crash during runtime, which is what the compiler tries to avoid. Code like this is considered "not type safe" and one of the major causes for bugs. Therefore, this isn't allowed in VS.NET.
To make this work, you'd have to change your code to this:
TestClass2 oTest = new TestClass2();
oTest.GetLongDate();
Now everything works fine, but you also limited yourself to the types you can assign to the "oForm" variable. This has some far-reaching implications when you attempt to write generic code. None of them are too critical, though, as you can get around them with implicit type conversions and other techniques. It's just something you have to be aware of so you can deal with the situation.
For VFP developers, this is all a bit confusing, because you're used to being able to call whatever methods and properties the current instance of objects happen to have. And if the source code attempts to call a method that isn't there, you definitely don't get the desired result. It certainly is nice having the compiler catch these errors right away, rather than having them come up after the applications is with the customer.
Namespaces and assemblies
In Visual FoxPro, you usually store your classes organized in class libraries. These could either be VCXs or PRGs. One EXE or DLL can have any number of class libraries (although they're tricky to use from outside the compiled application). In VS.NET, the closest equivalent of a class library would be a "Namespace." Just like a class library, a namespace can hold any number of classes. Also, each compiled VS.NET project (EXE or DLL "Assembly") can have any number of namespaces. Unlike Visual FoxPro, however, in VS.NET you usually refer to assemblies in their compiled form. In contrast, in VFP you usually include the source code version in projects.
One of the major differences between namespaces and VFP class libraries is that namespaces become part of the name of the class. Consider this C# example:
namespace MyNamespace
{
public class TestClass
{
}
}
Here's the VB.NET equivalent:
Public Namespace MyNamespace
Public Class TestClass
End Class
End Namespace
You could now instantiate a class called "TestClass." But this is only the informal name of that class. The real (fully qualified) name of the class is "MyNamespace.TestClass." Whether you have to use the fully qualified name of the class depends on the situation. Other classes within the same namespace can refer to your class as "TestClass." Other projects and namespaces need to use the fully qualified name. These names can be quite lengthy. For this reason, you can define that a certain namespace is to be assumed within a code file (.cs or .vb, the equivalent of a VFP .prg file). You can do this as follows (C#):
using MyNamespace;
Here's the VB.NET version:
Imports MyNamespace
After this declaration (which is located at the top of code files), you can use the short name (although it's still OK to use the fully qualified name). So this is a little like SET PROCEDURE TO or SET CLASSLIB TO. Note that there can be any number of using/Imports statements in a file (additionally, VB.NET lets you define a list of default namespaces to include in every file). You may have seen this before because VS.NET's designers use this technique often. When you create a Windows Forms application, for example, all sorts of namespaces are included right away (especially in a C# application, VB.NET hides some of these details).
In many ways, namespaces can be seen as "class libraries on steroids." It's much easier to maintain a large number of namespaces than it is to maintain a large number of class libraries. Namespaces aren't concerned with the physical location of a file. After you add an assembly to a project's references (think of it as "being included in the project"), the path to the actual file is irrelevant. You can move things around afterwards, and everything will still work. In fact, assemblies that contain the namespaces can even be loaded across the network or even the Internet. At the same time, those namespaces/assemblies aren't compiled into the project, which keeps file sizes small and makes updates easy. But doesn't that cause namespace-hell? No, VS.NET assemblies use a versioning mechanism that solves that problem once and for all (this will also be the subject of a future article).
Another great feature of namespaces is that they can be organized hierarchically. For example, you could define your namespace like this:
namespace Examples.VFPandDotNet.MyNamespace
{
public class TestClass
{
}
}
In the .NET Framework, this is used heavily. All the Windows-related classes are stored in a "System.Windows" namespace (which has a few sub-namespaces), all the Web-related classes are in the "System.Web" namespace, etc.
Note that class names within namespaces have to be unique. On the other hand, it's perfectly fine to have classes of the same name in different namespaces. Good examples of this are the DataGrid classes that exist for Windows Forms as well as Web Forms (ASP.NET). Things get confusing when two namespaces are used/included in the same file that contains a class of the same name. It's confusing for you, and even worse for the compiler. Therefore, you need to use fully qualified names in those scenarios. This is also the reason many designers use fully qualified names, even if the namespaces are used/included. After all, the designer can never be certain the developer won't introduce duplicate class names later. This is why, when you create a windows form and add a button to it, the button will always be referred to as "System.Windows.Forms.Button" and never just as "Button", although that would work in almost all scenarios.
Either way, you win
VS.NET is a great development environment. We certainly encourage you to investigate concepts and techniques described in this article. At the very least, you'll learn a few new things about object-oriented development that may be helpful even if you don't plan to work in any development environment other then Visual FoxPro. And you'll even end up writing better code because you'll know about things the VFP compiler won't catch for you.
If you do plan to use Visual Studio .NET, however, you'll add another tool in your arsenal, and the learning curve may not even be as big as you thought. We expect many of the concepts described here are somewhat familiar to you, even though they take on a different flavor. Compare this to someone from England moving to the U.S., or vice-versa. Either one can understand the overall concept of what happens and can function quite well in the new environment. However, a number of things are expressed a little differently, and a number of customs may appear rather odd. But, at the end of the day, the person is able to adapt and be productive. You'll find yourself in the same situation.
This article begins a series that will explain a number of things in much more detail than we were able to do here. Stay tuned, and if you have any questions, feel free to send us an e-mail.
By Markus Egger and Claudio Lassala