This section concerns primarily differences in the way that Visual Studio configures projects in C# and VB. These differences in turn often affect the operation of each language's compiler.
Whenever you generate a new project, each language adds either an AssemblyInfo.vb (for VB) or an AssemblyInfo.cs file (for C#). This file contains attributes for the assembly. Think of the assembly as the project. Assembly attributes are things like Title, Description, Version Number, etc. for the resulting EXE or DLL. Each language puts slightly different attributes in each file. The following code list shows each C# attribute, followed by the VB.NET equivalent:
[assembly: AssemblyTitle("")] //C#<Assembly: AssemblyTitle("")> 'VB[assembly: AssemblyDescription("")] //C#<Assembly: AssemblyDescription("")> 'VB[assembly: AssemblyConfiguration("")] //C#'VB .NET does not add this attribute
[assembly: AssemblyCompany("")] //C#<Assembly: AssemblyCompany("")> 'VB[assembly: AssemblyProduct("")] //C#<Assembly: AssemblyProduct("")> 'VB[assembly: AssemblyCopyright("")] //C#<Assembly: AssemblyCopyright("")> 'VB[assembly: AssemblyTrademark("")] //C#<Assembly: AssemblyTrademark("")> 'VB[assembly: AssemblyCulture("")] //C#'VB does not add this attribute
//C# does not add this attribute
<Assembly:CLSCompliant(True)> 'VB
//C# does not add this attribute
<Assembly: Guid("xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx")> 'VB[assembly: AssemblyVersion("1.0.*")] //C#<Assembly: AssemblyVersion("1.0.*")> 'VB[assembly: AssemblyDelaySign(false)] //C#
'VB does not add this attribute
[assembly: AssemblyKeyFile("")] //C#'VB does not add this attribute
[assembly: AssemblyKeyName("")] //C#'VB does not add this attribute
Let's discuss the attributes that are not added by default in both languages.
The System.Reflection.AssemblyConfiguration attribute in C# projects enables you to specify the target for the project, such as Debug, Release, or any custom target you declare. Targets are known in .NET as configurations. However, this attribute has little impact on the program. It is only useful if a program using reflection (functions to read a program's metadata) specifically looks for it. In other words, someone has to write custom logic to search for the attribute for the attribute to be meaningful.
System.Reflection.AssemblyCulture (in C# projects) is one of the pieces of information that the assembly resolver uses when locating an assembly in your system. The assembly resolver locates assemblies based on the assembly's name, the assembly's version, the assembly's culture, and the assembly's originator. It seems that the culture would therefore be an important attribute to include. However, the culture attribute should only be applied to satellite DLLs. Satellite DLLs are DLLs that have no executable code; they only have resources for strings, icons, etc. Supposing an application needs to display a localized message box, the application could load a particular satellite DLL based on the culture. If your program has executable code, then you must leave this attribute blank; otherwise, the compiler returns an error. The compiler will mark any DLL or EXE with executable IL (and therefore with the AssemblyCulture attribute set to blank) to Culture=neutral.
The System.Runtime.InteropServices.Guid attribute (added to VB.NET projects) is used for COM interop. Every type library (a type library describes the interfaces and classes your .NET assembly makes available to COM projects) has a GUID that identifies it, called a LIBID. This attribute lets you set the LIBID when you create a COM type library from your .NET assembly. The .NET Framework SDK ships with a tool called tlbexp.exe. This tool can read a .NET assembly's metadata and generate a COM type library from it. Then you can use the type library in VB 6 or in Visual C++ 6.0 with the #import directive.
Whenever you compile a .NET project, the IDE automatically generates one of these LIBIDs, so even if you do not have this attribute in your project, your type library will have a LIBID. However, unless you set this attribute explicitly, every time you compile you run the risk of the compiler generating a new number. Having a new GUID every time you recompile is not desirable. The GUID itself is generated using an algorithm that takes into consideration the name of the assembly and the names of all the public classes and interfaces, as well as the project's version number. Project GUIDs are not guaranteed to be unique. If two people in two different machines generate the same types of projects with the same names for each class and interface, and assign the same version number, they will both end up with the same LIBID. Adding this attribute means that you are taking full control of when that GUID gets changed.
The AssemblyDelaySign, AssemblyKeyFile, and AssemblyKeyName attributes (added to C# projects) have to do with digitally signing your assembly. Before adding an assembly to the global assembly cache (which contains assemblies that are shared among multiple projects), the assembly must be digitally signed. These attributes influence how the assembly is signed. Typically, these attributes should be added to projects whose resulting DLL will be added to the GAC. If you need to digitally sign an assembly, you must add these attributes to your source code (in VB by hand). An alternative to adding these attributes to your code is to compile your program with the command-line compiler and use the /delaysign, /keyfile, and /keycontainer switches.
A namespace is a prefix that is added to each class name in order to make the name of the class unique. Many times, companies use the company name plus the project name as a namespace name.
Both VB.NET and C# enable you to specify a default namespace
through a project setting. To get to this setting in VB.NET, first locate the
Solution Explorer window, right-click on the project name, and choose Properties
from the popup menu. A dialog appears with project settings; under Common
Properties
General, you will see a field
called Root namespace. Follow the same steps in C#, but the field in the Common
Properties
General dialog is called Default
namespace.
In C#, having a default namespace means that any time you ask
the IDE to add a new class file to your project, the wizard creates a file that
declares the default namespace, then adds the new class definition within the
namespace. For example, if your default namespace is WidgetsUSA, when you
choose Project
Add Class from the menu, the
wizard creates a source file that looks like the following:
namespace WidgetsUSA
{public class Class2
{}
}
If you change the default namespace setting, then any new files you generate will have the new namespace name; old files retain the previous namespace name. You could also set this property to blank, and then the wizard would not add a namespace declaration.
In VB.NET, when you assign a root namespace to your project, all classes in the project automatically become part of the root namespace. The wizard does not add a namespace definition to the source files; the setting takes effect at compile time. If you add a namespace declaration to the source explicitly, the compiler still appends the root namespace to the name of each class. If you set the root namespace to WidgetsUSA and write code that defines a Banking namespace with a class called Account, as follows:
Namespace Banking
Class Account
End Class
End Namespace
the full name of the Account class will be WidgetsUSA.Banking.Account. The root namespace will always be added to every class as a prefix. By default, VB sets the root namespace property to the project name. Because the setting only takes effect at compile time, if you change it in the middle of development, then the namespace is changed for every class. You can also leave the root namespace field blank.
Project Properties
Common
Properties
General has a setting called
Startup Object. Both C# and VB.NET have the same setting in the same location.
In C#, you can set this to either (Not
set), the default value, or to
the name of a class with a static Main procedure. If you set this option to (Not set)
then the compiler will look for one class that has a static Main procedure. If
you have more than one class with a static Main, you can't leave the setting as
(Not set); you must set it to a specific class or the compiler
will complain.
VB.NET has an option that reads Sub Main. This option is equivalent to (Not set) in C#. If you select this option, the compiler looks for a class or a module with a Sub Main. If you have two classes with Sub Main, the compiler complains. You then have to pick a specific class from the drop-down list.
A significant difference between the two languages concerns the handling of the startup form in a WinForms project. C# adds the following code to the first WinForm the wizard creates:
static void Main( )
{Application.Run(new Form1( ));
}
Since the form is a class, the static void Main procedure satisfies the Startup Object requirement. If you add another form with the wizard, the second form will not have a static Main method. If you want the second form to be the startup form, then you have to cut and paste the code from the first form into the second form. VB.NET takes a different approach. The wizard does not add any startup code, and you can set the Startup Object to any form class. Then, when you compile, VB will add startup code at the IL level to the form you selected as the Startup Object. In fact, the IL code looks like the C# code above.
Both languages enable you to add a custom icon for the
application. The icon setting in C# is under Project Properties
Common Properties
General
Application
icon; in VB, it is under Project Properties
Common
Properties
Build
Application
icon. C# lets you customize the default icon. When you generate a C# project,
the wizard adds a file called App.ico with the default icon to the
project files and changes the Application icon setting to point to this file.
In VB.NET this setting is set to (Default icon) by default. There's no icon
file that you can edit if you leave this set to the default.
As you may have seen in Section 1.2, both languages enable you to omit
the namespace name of a class by using the Imports/using
statement. The IDE offers an extra feature for VB projects. VB projects enable
you to add Imports statements
that apply to all files in the project through Project Properties
Common Properties
Imports.
Both languages enable you to add references to COM components. You do that by right-clicking on the References line in the Solution Explorer window and choosing Add Reference from the popup menu. You will then see the Add Reference dialog. From there, you can click on the COM tab and select a COM class. When you do so, the IDE creates a .NET Interop DLL (a wrapper assembly) that describes the interfaces and classes in the COM DLL. The IDE then copies the Interop DLL to the output directory of the application referencing it. Whenever you make a call through a .NET wrapper class, the call is forwarded to the COM class.
C# goes a step further by having two properties in the IDE,
under Project Properties
Common
Properties
General, that enable you to digitally sign
the resulting .NET DLL. They are Wrapper Assembly Key File and Wrapper Assembly
Key File Name. Why would you want to digitally sign the DLL? Because to share
the DLL among different executables, it is best to add the DLL to the Global
Assembly Cache (GAC), and to add it to the GAC you must digitally sign the DLL.
Thus if you generate a public/private pair key file, you can specify the name
of the file in the Wrapper Assembly Key File setting and Visual Studio .NET
will automatically use that file to digitally sign the resulting DLL.
VB projects do not have such a setting. To do the same thing in a VB project, you must create the Interop DLL by hand using a tool called tlbimp.exe that ships with the .NET Framework SDK (it should be already installed in your system if you installed Visual Studio .NET). tlbimp.exe has a command-line switch called /keyfile that lets you specify a public/private pair key file to use to digitally sign the Interop DLL. Once you generate the DLL with tlbimp.exe, you then add a regular .NET Reference to your project instead of a COM Reference.
Both C# and VB.NET support selected compiler constants. You can define compiler constants by adding a command to your source code or by adding the constants to your project settings through the IDE. The following table shows the different commands you use to define and test for compiler constants:
|
C# |
Description |
|
|
#Const |
#define |
Defines the constant within code. For example: #Const TestNumber=45 or #define Production In C# you cannot set the constant to value. It is either defined or undefined. |
|
#If...Then |
#if |
Tests for the constant. If the result is true, the code following the #if is compiled; otherwise, the code is omitted. |
|
#Else |
#else |
Compile alternate code. |
|
#ElseIf |
#elif |
Add a second condition. |
|
N/A |
#undef |
Undefine a constant. For example: #undef TestNumber |
The following code fragments show the use of the compiler directives in each language:
Sub ReadProgramSettings( )
#If DEBUG Then
#Const SettingsFile = _
"C:\Code\Config\Settings.txt"
ReadSettingsFromTextFile( )
#Else
ReadSettingsFromDatabase( )
#End If
End Sub
#If DEBUG Then
Sub ReadSettingsFromTextFile( )
'code to read settings from
'SettingsFile
End Sub
#Else
Sub ReadSettingsFromDatabase( )
End Sub
#End If
How does that differ from a regular If statement? The #If is evaluated at compile time rather than at runtime. Only the code within the block that meets the criteria will be compiled.
The compiler constant DEBUG
is defined through a setting in the IDE: Project Properties
Configuration Properties
Define DEBUG
constant. If DEBUG is defined, the resulting VB code will be:
Sub ReadProgramSettings( )
#Const SettingsFile = _
"C:\Code\Config\Settings.txt"
ReadSettingsFromTextFile( )
End Sub
Sub ReadSettingsFromTextFile( )
'code to read settings from
'SettingsFile
End Sub
Otherwise, the code would look like the following:
Sub ReadProgramSettings( )
ReadSettingsFromDatabase( )
End Sub
Sub ReadSettingsFromDatabase( )
End Sub
The C# equivalent code is the following:
void ReadProgramSettings( )
{#if DEBUG
ReadSettingsFromTextFile( );
#else
ReadSettingsFromDatabase( );
#endif
}
#if DEBUG
void ReadSettingsFromTextFile( )
{//code to read settings from
//SettingsFile
}
#else
void ReadSettingsFromDatabase( )
{}
#endif
One of the biggest differences between the two languages is that in VB compiler constants must have values. Also the VB #If statement lets you write a full conditional statement. You could do the following, for example:
#If OutputFile = "C:\Windows\File.txt" _
And Trace=True Then
In C# constants are either defined or undefined, and they can't be compared to literal values. In C#, you simply write:
#define VAR
#if VAR
without assigning VAR a value.
Probably one of the biggest differences
between C# and VB.NET when it comes to project settings is that VB has three
unique properties: Option Explicit, Option Strict, and Option Compare. You can
find these properties in Project Properties
Common Properties
Build. Option Explicit controls whether variable declaration is
required. If set to Off, you can use a variable without first declaring it. The
type of such a variable will be System.Object. I can almost see some C#
developers' jaws drop in disbelief—what kind of language is VB that it lets you
use variables without declaring them first? To be honest, most experienced VB
developers always set this option to On, and it is now (actually for the first
time in VB's history) set to On by default. There is no equivalent in C# for
this feature.
The second setting is Option Strict. This is a new setting for VB developers. If you set Option Strict to Off, you do not need to assign a type to your variables. In other words, it is perfectly legal to write code that says:
Dim Acct = New CChecking( )
In that case, Acct will be of type System.Object. This feature has two other side effects. One is that with Option Strict On, the compiler will issue a compile-time error if it detects a narrowing conversion or if it cannot guarantee that the conversion will succeed. The developer can fix the error by using an explicit cast. In VB explicit casts are done with the CType function or an equivalent. (See Section 1.2.28 for a full explanation.) The only reason this option should be set to off is to use late binding (see Section 1.5.5 for details).
The third option in VB.NET, Option Compare, was explained in detail in Section 1.3.13.
Both compilers generate errors (of course), and both generate warnings as well, but the C# compiler can generate different types of warnings. Warnings come in different levels, 0 through 4. Warning level 0 means that no warnings are reported. Warning level 1 means that only severe warnings are reported.
VB.NET projects let you set the warning level
to either 0 or 1. This is controlled through Project Properties
Configuration Properties
Build
Enable build warnings (checked by default). C# lets you set the
warning level through Project Properties
Configuration Properties
Build
Warning Level. A warning level of 2 means that the compiler also
displays warnings that have to do with method hiding. Warning level 3
also warns about expressions that are always true or always false. Warning
level 4 (the default) displays informational warnings.