Notes based on   Christoph Wille’s, book Presenting C#

I weeded unnecessary verbage from Wille’s notes.

·         Chapter 1, "Introduction to C#"—You are taken on a tour of C#, and this chapter answers questions about why you should consider learning C#.

·         Chapter 2, "The Underpinnings—The NGWS Runtime"—You are introduced to how the NGWS Runtime provides the infrastructure for your C# code to run.

·         Chapter 3, "Your First C# Application"—You create your very first C# application and (how could it be otherwise?) it is a "Hello World" application.

·         Chapter 4, "C# Types"—You discover the various types that you can use in your C# applications. You explore the differences between value types and reference types, and how boxing and unboxing works.

·         Chapter 5, "Classes"—You tap the real power of C#, which is object-oriented programming with classes. You learn a great deal about constructors, destructors, methods, properties, indexers, and events.

·         Chapter 6, "Control Statements"—You take over the control of flow in your application. You explore the various selection and iteration statements provided by C#.

·         Chapter 7, "Exception Handling"—You acquire the skills to write applications that are good citizens in the world of the NGWS Runtime, by implementing proper exception handling.

·         Chapter 8, "Writing Components in C#"—You build a component in C# that can be used by clients across languages because you leverage the NGWS Runtime.

·         Chapter 9, "Configuration and Deployment"—You learn how conditional compilation works in C#, as well as how to automatically generate documentation from your C# source code. Additionally, this chapter introduces the versioning technology of NGWS.

·         Chapter 10, "Interoperating with Unmanaged Code"—You discover how you can use unmanaged code from inside C#, and how unmanaged code can interoperate with your C# components.

·         Chapter 11, "Debugging C# Code"—You acquire the skills to use the debugging tools provided in the SDK to pinpoint and fix bugs in your C# applications.

·         Chapter 12, "Security"—You explore the security concepts of the NGWS Runtime. You learn about code-access security and role-based security.

 

Chapter 1 Introduction to C#

Why Another Programming Language?

C# is intended to be the premier language for writing NGWS (Next Generation Windows Services) applications in the enterprise computing space. The programming language C# derives from C and C++; however, it is modern, simple, entirely object-oriented, and type-safe. If you are a C/C++ programmer, your learning curve will be flat.

Contributing to the ease of use is the elimination of certain features of C++: no more macros, no templates, and no multiple inheritance. The aforementioned features create more problems than they provide benefit—especially for enterprise developers.

New features for added convenience are strict type safety, versioning, garbage collection, and many more. All these features are targeted at developing component-oriented software. Although you don't have the sheer power of C++, you become more productive faster.

 C# based on key points in the following sections:

·         Simple

·         Modern

·         Object-oriented

·         Type-safe

·         Versionable

·         Compatible

·         Flexible

Simple

Pointers are a prominent feature that is missing in C#. By default, you are working with managed code, where unsafe operations, such as direct memory manipulation, are not allowed. I don't think any C++ programmer can claim never to have accessed memory that didn't belong to him via a pointer.

In C++, you have ::, ., and -> operators that are used for namespaces, members, and references. For a beginner, operators make for yet another hard day of learning. C# does away with the different operators in favor of a single one: the . (the "dot"). All that a programmer now has to understand is the notion of nested names.

C#  provides a unified type system. This type system enables you to view every type as an object, be it a primitive type or a full-blown class. In contrast to other programming languages, treating a simple type as an object does not come with a performance penalty because of a mechanism called boxing and unboxing. Boxing and unboxing is explained later in detail, but basically, this technique provides object access to simple types only when requested.

Integer and Boolean data types are now two entirely different types. That means a mistaken assignment in an if statement is now flagged as an error by the compiler because it takes a Boolean result only. No more comparison-versus-assignment errors!

C# also gets rid of redundancies that crept into C++ over the years. Such redundancies include, for example, const versus #define, different character types, and so on. Commonly used forms are available in C#, whereas the redundant forms were eliminated from the language.

Modern

C# was designed to be the premier language for writing NGWS (Next Generation Windows Services) applications. You will find many features that you had to implement yourself in C++, or that were simply unavailable.

You get a new decimal data type that is targeted at monetary calculations. You can easily create new ones specifically crafted for your application.

The entire memory management is no longer your duty—the runtime of NGWS provides a garbage collector that is responsible for memory management in your C# programs. Because memory and your application are managed, it is imperative that type safety be enforced to guarantee application stability.

Exception handling is a main feature of C#. The difference from C++, however, is that exception handling is cross-language (another feature of the runtime).

Security is a top requirement for a modern application. C# provides metadata syntax for declaring capabilities and permissions for the underlying NGWS security model. Metadata is a key concept of the NGWS runtime, and the next chapter deals with its implications in more depth.

Object-Oriented

C#, of course, supports all the key object-oriented concepts such as encapsulation, inheritance, and polymorphism. The entire C# class model is built on top of the NGWS (Next Generation Windows Services) runtime's Virtual Object System (VOS), which is described in the next chapter. The object model is part of the infrastructure, and is no longer part of the programming language.

There are no more global functions, variables, or constants. Everything must be encapsulated inside a class, either as an instance member (accessible via an instance of a class—an object) or a static member (via the type). This makes your C# code more readable and also helps to reduce potential naming conflicts.

The methods you can define on classes are, by default, non virtual (they cannot be overridden by deriving classes). The main point of this is that another source of errors disappears—the accidental overriding of methods. For a method to be able to be overridden, it must have the explicit virtual modifier. This behavior not only reduces the size of the virtual function table, but also guarantees correct versioning behavior.

C# also supports the private, protected, and public access modifiers, and also adds a fourth one: internal. Details about these access modifiers are presented in Chapter 5, "Classes."

 C# allows only one base class.  For multiple inheritance, you can implement interfaces.

A question that might come up is how to emulate function pointers when there are no pointers in C#. The answer to this question is delegates, which provide the underpinnings for the NGWS runtime's event model.

Type-Safe

 C# implements strictest type safety to protect itself and the garbage collector. Therefore, you must abide by a few rules in C# with regard to variables:

·         You cannot use uninitialized variables. For member variables of an object, the compiler takes care of zeroing them. For local variables, you are incharge. However, if you use an uninitialized variable, the compiler will tell you so. The advantage is that you get rid of those errors when using an uninitialized variable to compute a result and you don't know how these funny results are produced.

·         C# does away with unsafe casts. You cannot cast from an integer to a reference type (object, for example), and when you downcast, C# verifies that this cast is okay. (That is, that the derived object is really derived from the class to which you are down casting it.)

·         Bounds checking is part of C#. It is no longer possible to use that "extra" array element n, when the array actually has n-1 elements. This makes it impossible to overwrite unallocated memory.

·         Arithmetic operations could overflow the range of the result data type. C# allows you to check for overflow in such operations on either an application level or a statement level. With overflow checking enabled, an exception is thrown when an overflow happens.

·         Reference parameters that are passed in C# are type-safe.

Versionable

The problem stems from the fact that multiple applications install different versions of the same DLL to the computer. Sometimes, older applications happily work with the newer version of the DLL; however, most of the time, they break. Versioning is a real pain today.

"Writing Components in C#," the versioning support for applications you write is provided by the NGWS runtime. C# does its best to support this versioning. Although C# itself cannot guarantee correct versioning, it can ensure that versioning is possible for the programmer. With this support in place, a developer can guarantee that as his class library evolves, it will retain binary compatibility with existing client applications.

Compatible

C# allows you access to different APIs, with the foremost being the NGWS Common Language Specification (CLS). The CLS defines a standard for interoperation between languages that adhere to this standard. To enforce CLS compliance, the compiler of C# checks that all publicly exported entities comply, and raises an error if they do not.

Of course, you also want to be able to access your older COM objects. The NGWS runtime provides transparent access to COM

The good news is that C# supports OLE Automation, without bothering you with details.

Finally, C# enables you to inter operate with C-style APIs. Any entry point in a DLL—given its C-styledness—is accessible from your applications. This feature for accessing native APIs is called Platform Invocation Services (Pinvoke).

Flexible

The last paragraph of the previous section might have raised an alert with C programmers. You might ask, "Aren't there APIs to which I have to pass a pointer?" You are right. There are not only a few such APIs, but quite a large number (a small understatement). This access to native WIN32 code sometimes makes using unsafe classic pointers mandatory (although some of it can be handled by the support of COM and PInvoke).

Although the default for C# code is safe mode, you can declare certain classes or only methods of classes to be unsafe. This declaration enables you to use pointers, structs, and statically allocated arrays. Both safe code and unsafe code run in the managed space, which implies that no marshaling is incurred when calling unsafe code from safe code.

What are the implications of dealing with your own memory in unsafemode? Well, the garbage collector, of course, may not touch your memory locations and move them just as it does for managed code. Unsafe variables are pinned into the memory block managed by the garbage collector.

 

Chapter 2 The NGWS Runtime

The NGWS Runtime

You are provided with a runtime environment by NGWS, the NGWS runtime. This runtime manages the execution of code, and it provides services that make programming easier. As long as the compiler that you use supports this runtime, you will benefit from this managed execution environment.

C# compiler supports the NGWS runtime. However, it is not the only compiler that supports the NGWS runtime; Visual Basic and C++ do so also. The code that these compilers generate for NGWS runtime support is called managed code. The benefits your applications gains from the NGWS runtime are

·         Cross-language integration (through the Common Language Specification)

·         Automatic memory management (garbage collection)

·         Cross-language exception handling (unified unwinding)

·         Enhanced security (including type safety)

·         Versioning support (the end of "DLL hell")

·         Simplified model for component interaction

For the NGWS runtime to provide all these benefits, the compiler must emit metadata along with the managed code. The metadata describes the types in your code, and is stored along with your code (in the same PE—portable executable—file).

As you can see from the many cross-language features, the NGWS runtime is mainly about tight integration across multiple different programming languages. This support goes as far as allowing you to derive a C# class from a Visual Basic object (given that certain prerequisites that I'll discuss later are met).

One feature that C# programmers will like is that they don't have to worry about memory management—namely the all-famous memory leaks. The NGWS runtime provides the memory management, and the garbage collector releases the objects or variables when their lifetimes are over—when they are no longer referenced

Because managed applications contain metadata, the NGWS runtime can use this information to ensure that your application has the specified versions of everything it needs. The net result is that your code is less likely to break because some dependency is not met. Another advantage of the metadata approach is that type information resides in the same file where the code resides—no more problems with the Registry!

The remainder of this section is split into two parts, each of which discusses various aspects of the NGWS runtime until your C# application is executed:

·         Intermediate Language (IL) and metadata

·         JITters

Intermediate Language and Metadata

The managed code generated by the C# compiler is not native code, but is Intermediate Language (IL) code. This IL code itself becomes the input for the managed execution process of the NGWS runtime. The ultimate advantage of IL code is that it is CPU independent; however, that means you need a compiler on the target machine to turn the IL code into native code.

The IL is generated by the compiler, but it is not the only thing that is provided for the runtime by the compiler. The compiler also generates metadata about your code, which tells the runtime more about your code, such as the definition of each type, and the signatures of each type's member as well as other data. Basically, metadata is what type libraries, Registry entries, and other information are for COM—however, the metadata is packaged directly with the executable code, not in disparate locations.

The IL and the metadata are placed in files that extend the PE format used for .exe and .dll files. When such a PE file is loaded, the runtime locates and extracts the metadata and IL from it.

Categories of IL instructions that exist. Although this is not meant to be a complete list, nor do you need to learn it by heart to understand, it gives you a necessary insight into the foundation on which your C# programs depend:

·         Arithmetic and logical operations

·         Control flow

·         Direct memory access

·         Stack manipulation

·         Argument and local variables

·         Stack allocation

·         Object model

·         Values of instantiable types

·         Critical region

·         Arrays

·         Typed locations

JITters

The managed code generated by C#—and other compilers capable of generating managed code—is IL code. Although the IL code is packaged in a valid PE file, you cannot execute it unless it is converted to managed native code. That is where the NGWS runtime JIT Just-in-Time (JIT) compilers—which are also referred to as JITters—come into the picture.

Why would you bother compiling code just-in-time? Why not take the whole IL PE file and compile it to native code? The answer is time—the time that is necessary to compile the IL code to CPU-specific code. It is much more efficient to compile as you go because some code pieces might never be executed

Technically speaking, the whole process works like this: When a type is loaded, the loader creates and attaches a stub to each method of the type. When a method is called for the first time, the stub passes control to the JIT. The JIT compiles the IL to native code, and changes the stub to point to the cached native code. Subsequent calls will execute the native code. At some point, all IL is converted to native code, and the JITter sits idle.

On Windows platforms, NGWS runtime ships with three different JITters:

·         JIT—This is the default JIT compiler used by the NGWS runtime. It is an optimizing compiler back end, which performs a data flow analysis up front and creates highly optimized, managed native code as output. The JIT can cope with unrestricted sets of IL instructions; however, the resource requirements are quite considerable. The main constraints are the memory footprint, the resulting working set, and the time it takes to perform the optimizations.

·         EconoJIT—In contrast to the main JIT, EconoJIT is targeted at high-speed conversion of IL to managed native code. It allows for caching of the generated native code; however, the output code isn't as optimized as the code produced by the main JIT. The advantage of fast code generation strategy pays off when memory is constrained—you can fit even large IL programs into this code cache by permanently discarding unused jitted code. Because JITting is fast, execution speed is still rather high.

·         PreJIT—The PreJIT operates much more like a traditional compiler, although it is based on the main JIT compiler. It runs when an NGWS component is installed, and compiles the IL code to managed native code. The end results are, of course, faster loading time and faster application start time. (No more JITting is necessary.)

Two of the listed JITters are runtime JITters. However, how can you determine which JIT is to be used, and how it uses memory? There is a small utility named the JIT Compiler Manager (jitman.exe), which resides in the bin folder of the NGWS SDK installation folder

Figure 2.1 The JIT Compiler Manager enables you to set various performance-related options.

Although it is a small dialog box, the options you can choose in it are quite powerful. Each of the options is described in the following list:

·         Use EconoJIT only—When this option is unchecked, the NGWS runtime uses the regular JIT compiler by default. The differences between the two JITters are explained earlier in this section.

·         Max Code Pitch Overhead (%)—This setting pertains only to EconoJIT. It controls the percentage of time spent JITting versus executing code. If the threshold is exceeded, the code cache is expanded to reduce the amount of time spent JITting.

·         Limit Size of Code Cache—This setting is unchecked by default. Not checking this option means that the cache will use as much memory as it can get. If you want to limit the cache size, enable this option, which allows you to use the Max Size of Cache (bytes) option.

·         Max Size of Cache (bytes)—Controls the maximum size of the buffer that holds JITted code. Although you can very strictly limit this size, you should take care that the largest method fits this cache because otherwise the JIT compilation for this method will fail!

·         Optimize For Size—Tells the JIT compiler to go for smaller code instead of faster code. By default, this setting is turned off.

·         Enable Concurrent GC [garbage collection]—By default, garbage collection runs on the thread on which the user code runs. That means when GC happens, a slight delay in responsiveness might be noticeable. To prevent this from happening, turn on concurrent GC. Notice that concurrent garbage collection is slower than standard GC, and that it is available only on Windows 2000 at the time of this writing.

You can experiment with the different settings when creating projects with C#. When creating UI-intensive applications, you'll see the biggest difference by enabling concurrent GC.

Chapter 2 The Underpinnings--The NGWS Runtime
by Christoph Wille, from the Book Presenting C#
JUL 07, 2000

The Virtual Object System

So far, you have seen only how the NGWS runtime works, but not the technical background of how it works and why it works the way it does. This section is all about the NGWS Virtual Object System (VOS).

The rules the NGWS runtime follow when declaring, using, and managing types are modeled in the Virtual Object System (VOS). The idea behind the VOS is to establish a framework that allows cross-language integration and type safety, without sacrificing performance when executing code.

The framework I mentioned is the foundation of the runtime's architecture. To help you better understand it,I will outline four areas that are important to know about when developing C# applications and components:

·         The VOS type system—Provides a rich type system that is intended to support the complete implementation of a wide range of programming languages.

·         Metadata—Describes and references the types defined by the VOS type system. The persistence format of metadata is independent of the programming language; therefore, metadata lends itself as an interchange mechanism for use between tools as well as with the Virtual Execution System of NGWS.

·         The Common Language Specification (CLS)—The CLS defines a subset of types found in the VOS, as well as usage conventions. If a class library abides by the rules of the CLS, it is guaranteed that the class library can be used in all other programming languages that implement the CLS.

·         The Virtual Execution System (VES)—This is the actual real-life implementation of the VOS. The VES is responsible for loading and executing programs that were written for the NGWS runtime.

Together, these four parts comprise the NGWS runtime architecture. Each of these parts is described at length in the following sections.

The VOS Type System

The VOS type system provides a rich type system that is intended to support the complete implementation of a wide range of programming languages. Therefore, the VOS has to support object-oriented languages as well as procedural programming languages.

Today, there are many similar—but subtly incompatible—types around. Consider the integer data type, for example: In VB, it is 16 bits in length, whereas in C++, it is 32 bits. There are many more examples, especially the data types used for date and time, and database types. This incompatibility unnecessarily complicates the creation and maintenance of distributed applications, especially when multiple programming languages are involved.

Another problem is that because of the subtle differences in programming languages, you cannot reuse a type created in one language in a different one. (COM partially solves this with the binary standard of interfaces.) Code reuse is definitely limited today.

The biggest hurdle for distributed applications is that the object models of the various programming languages are not uniform. Almost everything differs: events, properties, persistence—you name it.

The VOS is here to change that picture. The VOS defines types that describe values and specify a contract that all values of the type must support. Because of the aforementioned support for object-oriented (OO) and procedural programming languages, two kinds of entities exist: values and objects.

For a value, the type describes the storage representation as well as operations that can be performed on it. Objects are more powerful because the type is explicitly stored in its representation. Each object has an identity that distinguishes it from all other objects. The different VOS types supported by C# are presented in Chapter 4, "C# Types."

Metadata

Although metadata is used to describe and reference the types defined by the VOS type system, it is not exclusively locked to this single purpose. When you write a program, the types you declare—be they value types or reference types—are introduced to the NGWS runtime type system by using type declarations, which are described in the metadata stored inside the PEexecutable.

Basically, metadata is used for various tasks: To represent the information that the NGWS runtime uses to locate and load classes, to layout instances of these classes in memory, to resolve method invocations, to translate IL to native code, to enforce security, and to set up runtime context boundaries.

You do not have to care about the generation of the metadata. Metadata generation is done for you by C#'s code-to-IL compiler (not theJIT compiler). The code-to-IL compiler emits the binary metadata information into the PE file for you, and it does so in a standardized way—not like C++ compilers that create their own decorated names for exported functions.

The main advantage you gain from combining the metadata with the executable code is that the information about the types is persisted along with the type itself, and it is no longer spread across multiple locations. It also helps to address versioning problems that exist in COM. Furthermore, in NGWS runtime, you can use different versions of a library in the same context because the libraries are referenced not by the Registry, but by the metadata contained in the executable code.

The Common Language Specification

The Common Language Specification is not exactly a part of the Virtual Object System—it is a specialization. The CLS defines a subset of types found in the VOS, as well as usage conventions that must be followed to be CLS-compliant.

So, what is this fuss all about? If a class library abides by the rules of CLS, it is guaranteed to be usable by clients of other programming languages that also adhere to the CLS. CLS is about language interoperability. Therefore, the conventions must be followed only on the externally visible items such as methods, properties, events, and soon.

The advantage of what I have described is that you can do the following: Write a component in C#, derive from it in Visual Basic and, because the functionality added in VB is so great, derive again from the VB class in C#. This works as long as all the externally visible (accessible) items abide by the rules of CLS.

The code I present in this book does not care about CLS compliance. However, you should care about CLS compliance when building your class library. Therefore, I have provided Table 2.1 to define the compliance rules for types and items that might be externally visible.

This list is not complete—it contains only some of the most-important items. I don't point out the CLS compliance of every type presented in this book, so it is a good idea to at least glance over the table to see which functionality is available when you are hunting for CLS compliance. Don't worry if you are not familiar with every term in this table—you will learn about these terms during the course of this book.

Table 2.1 Types and Features in the Common LanguageSpecification

Primitive Types

bool

char

byte

short

int

long

float

double

string

object(The mother of all objects)

Arrays

The dimension must be known (>=1), and the lower bound must be zero.

Element type must be a CLS type.

Types

Can be abstract or sealed.

Zero or more interfaces can be implemented. Different interfaces are allowed to have methods with the same name and signature.

A type can be derived from exactly one type. Member overriding and hiding are allowed.

Can have zero or more members, which are fields, methods, events, or types.

The type can have zero or more constructors.

The visibility of the type can be public or local to the NGWS component; however, only public members are considered part of the interface of the type.

All value types must inherit from System. ValueType. The exception is an enumeration—it must inherit from System. Enum.

Type Members

Type members are allowed to hide or override other members in another type.

The types of both the arguments and return values must be CLS-compliant types.

Constructors, methods, and properties can be overloaded.

A type can have abstract members, but only as long as the type is not sealed.

Methods

A method can be either static, virtual, or instance.

Virtual and instance methods can be abstract or have an implementation. Static methods must always have an implementation.

Virtual methods can be final (or not).

Fields

Can be static or nonstatic.

Static fields can be literal or initialize-only.

Properties

Can be exposed as get and set methods instead of using property syntax.

The return type of get and the first argument of the set method must be the same CLS type—the type of the property.

Properties must differ by name; a different property type is not sufficient for differentiation.

Because property access is implemented with methods, you cannot implement methods named get_PropertyName and set_PropertyName ifPropertyName is a property defined in the same class.

Properties can be indexed.

Property accessors must follow this naming pattern:get_PropName, set_PropName.

Enumerations

Underlying type must be byte, short, int, or long.

Each member is a static literal field of the enum's type.

An enumeration cannot implement any interfaces.

You are allowed to assign the same value to multiple fields.

An enumeration must inherit from System. Enum (performed implicitly in C#).

Exceptions

Can be thrown and caught.

Self-programmed exceptions must inherit from System.Exception.

Interfaces

Can require implementation of other interfaces.

An interface can define properties, events, and virtual methods. The implementation is up to the deriving class.

Events

Add and remove methods must be either both provided or both absent. Each of these methods takes one parameter, which is a class derived from System.Delegate.

Must following this naming pattern: add_EventName,remove_EventName, and raise_EventName.

Custom Attributes

Can use only the following types: Type, string, char, bool, byte, short, int, long, float, double, enum (of a CLS type), and object.

Delegates

Can be created and invoked.

Identifiers

An identifier's first character must come from a restricted set.

It is not possible to distinguish two or more identifiers solely by case in a single scope (no case sensitivity).

The Virtual Execution System

The Virtual Execution System implements the Virtual Object System. The VES is created by implementing an execution engine (EE) that is responsible for the runtime of NGWS. This execution engine executes your applications that were written and compiled in C#.

The following components are part of the VES:

·         The Intermediate Language (IL)—It is designed to be easily targeted by a wide range of compilers. Out of the box, you get C++, Visual Basic, and C# compilers that are capable of generating IL.

·         Loading managed code—This includes resolving names, laying out classes in memory, and creating the stubs that are necessary for the JIT compilation. The class loader also enforces security by performing consistency checks, including the enforcement of certain accessibility rules.

·         Conversion of IL to native code via JIT—The IL code is not designed as a traditional interpreted byte code or tree code. IL conversion is really a compilation.

·         Loading metadata, checking type safety, and integrity of the methods.

·         Garbage collection (GC) and exception handling—Both are services based on the stack format. Managed code enables you to trace the stack at runtime. For the runtime to understand the individual stack frames, a code manager has to be provided either by the JITter or the compiler.

·         Profiling and debugging services—Both of these depend on information produced by the source language compiler. Two maps must be emitted: a map from source language constructs to addresses in the instruction stream, and a map from addresses to locations in the stack frame. These maps are recomputed when conversion from IL to native code is performed.

·         Management of threads and contexts, as well as remoting—The VES provides these services to managed code.

The list provided is not complete, but it is enough for you to understand how the runtime is backed by the infrastructure provided by the VES. There certainly will be books dedicated completely to the NGWS runtime, and those books will drill deeper into each topic.

Summary

In this chapter, I took you on a tour of the NGWS runtime. I described how it works for you when creating, compiling, and deploying C# programs. You learned about the Intermediate Language (IL), and how metadata is used to describe the types that are compiled to IL. Both metadata and IL are used by JITters to examine and execute your code. You can even choose which JITters to use to execute your application.

The second part of this chapter dealt with the theory of why the runtime behaves the way it does. You learned about the Virtual Object System (VOS), and the parts that comprise it. Most interesting for class library designers is the Common Language Specification (CLS), which sets up rules for language interoperation based on the VOS. Finally, you saw how the Virtual Execution System (VES) is an implementation of the VOS by the NGWS runtime.

 

 

 

 

Chapter 3 Your First C# Application

Chapter 3 Your First C# Application

Choosing an Editor

You have several choices for an editor. You could reconfigure your  Visual C++ 6.0 to work with C# source files. A second option is to work with the new Visual Studio 7. Third, you can use any third-party programmer's editor, preferably one that supports line numbers, color coding, tool integration (for the compiler), and a good search function. One example of such a tool is CodeWright, which is shown in Figure 3.1.

Figure 3.1 CodeWright is one of many possible editors you can use for creating C# code files.

Of course, none of the mentioned editors is mandatory to create a C# program—Notepad will definitely do. However, if you are considering writing larger projects, it is a good idea to switch.

Chapter 3 Your First C# Application

The Hello World Code

Listing 3.1 The Hello World Program at Its Simplest

 1: class HelloWorld
 2: {
 3:  public static void Main()
 4:  {
 5:   System.Console.WriteLine("Hello World");
 6:  }
 7: }

In C#, code blocks (statements) are enclosed by braces ({ and }). Therefore, even if you don't have prior experience in C++, you can tell that the Main() method is part of the HelloWorld class statement because it is enclosed in the angle brackets of its definition.

The entry point to a C# application (executable) is the static Main method, which must be contained in a class. There can be only one class defined with this signature, unless you advise the compiler which Main method it should use (otherwise, a compiler error is generated).

In contrast to C++, Main has a capital M, not the lowercase you are already used to. In this method, your program starts and ends. You can call other methods—as in this example, for text output—or create objects and invoke methods on those.

As you can see, the Main method has a void return type:

public static void Main()

Although C++ programmers definitely feel at home looking at these statements, programmers of other languages might not. First, the public access modifier tells us that this method is accessible by everyone—which is a prerequisite for it to be called. Next, static means that the method can be called without creating an instance of the class first—all you have to do is call it with the class's name:

HelloWorld.Main();

However, I do not recommend executing this code in the Main method. Recursions cause stack overflows.

Another important aspect is the return type. For the method Main, you have a choice of either void (which means no return value at all), or int for an integer result (the error level returned by an application). Therefore, two possible Main methods look like

public static void Main()
public static int Main()

The command-line parameters array that can be passed to an application. This looks like

public static void Main(string[] args)

In contrast to C++, the application path is not part of this array. Only the parameters are contained in this array.

System.Console.WriteLine("Hello World");

If it weren't for the System part, one would immediately be able to guess that WriteLine is a static method of the Console object. So what does this System stand for? It is the namespace (scope) in which the Console object is contained. Because it's not really practical to prefix the Console object with this namespace part every time, you can import the namespace in your application as shown in Listing 3.2.

Listing 3.2 Importing the Namespace in Your Application

 1: using System;
 2: 
 3: class HelloWorld
 4: {
 5:  public static void Main()
 6:  {
 7:   Console.WriteLine("Hello World");
 8:  }
 9: }

All you have to do is add a using directive for the System namespace. From then on, you can use elements of that namespace without having to qualify them. There are many namespaces in the NGWS framework, and we will explore only a few objects from this huge pool.

Chapter 3 Your First C# Application

Compiling the Application

Because the NGWS runtime ships with all compilers (VB, C++, and C#), you do not need to buy a separate development tool to compile your application to IL (intermediate language). However, if you have never compiled an application using a command-line compiler (and know makefiles only by name and not by heart), this will be a first for you.

Open a command prompt and switch to the directory in which you saved helloworld.cs. Issue the following command:

csc helloworld.cs

helloworld.cs is compiled and linked to helloworld.exe. Because the source code is error-free (of course!), the C# compiler does not complain and, as shown in Figure 3.2, completes without hiccup.

Figure 3.2 Compile your application using the command-line compiler csc.exe.

Now you are ready to run your very first application written in C#. Simply issue helloworld at the command prompt. The output generated is "Hello World".

Before moving on, I want to get a little bit fancy about your first application and use a compiler switch:

csc /out:hello.exe helloworld.cs

This switch tells the compiler that the output file is to be named hello.exe. It's really not a big deal, but it is an apprentice piece for future compiler use in this book.

Input and Output

So far, I demonstrated only simple constant string output to the console. Although this book introduces concepts of C# programming and not user-interface programming, I need to get you up to speed on some simple console input and output methods--the C equivalents of scanf and printf, or the C++ equivalents of cin and cout.

You need only to be able to read user input and present some information to the user. Listing 3.3 shows how to read a requested name input from the user, and print a customized "Hello" message.

Listing 3.3 Reading Input from the Console

 1: using System;
 2: 
 3: class InputOutput
 4: {
 5:  public static void Main()
 6:  {
 7:   Console.Write("Please enter your name: ");
 8:   string strName = Console.ReadLine();
 9:   Console.WriteLine("Hello " + strName);
10:  }
11: }

Line 7 uses a new method of the Console object for presenting textual information to the user: the Write method. Its only difference from the WriteLine method is that Write does not add a line break to the output. I used this approach so that the user can enter the name on the same line as the question.

After the user enters his name (and presses the Enter key), the input is read into a string variable using the ReadLine method. The name string is concatenated with the "Hello " constant string and presented to the user with the already familiar WriteLine.

You are almost finished with learning the necessary input and output functionality of the NGWS framework. However, you need one thing for presenting multiple values to the user: writing out a formatted string to the user. One example is shown in Listing 3.4.

Listing 3.4 Using a Different Output Method

 1: using System;
 2: 
 3: class InputOutput
 4: {
 5:  public static void Main()
 6:  {
 7:   Console.Write("Please enter your name: ");
 8:   string strName = Console.ReadLine();
 9:   Console.WriteLine("Hello {0}",strName);
10:  }
11: }

Line 9 contains a Console.WriteLine statement that uses a formatted string. The format string in this example is

"Hello {0}"

The {0} is replaced for the first variable following the format string in the argument list of the WriteLine method. You can format up to three variables using this technique:

Console.WriteLine("Hello {0} {1}, from {2}", 
strFirstname, strLastname, strCity);

Of course, you are not limited to supplying only string variables. You can supply any type.

 

Chapter 3 Your First C# Application

Adding Comments

When writing code, you should also write comments to accompany that code—with notes about implementation details, change history, and so on. Although what information (if any) you provide in comments is up to you, you must stick to C#'s way of writing comments. Listing 3.5 shows the two different approaches you can take.

Listing 3.5 Adding Comments to Your Code

 1: using System;
 2: 
 3: class HelloWorld
 4: {
 5:  public static void Main()
 6:  {
 7:   // this is a single-line comment
 8:   /* this comment spans 
 9:   multiple lines */
10:   Console.WriteLine(/*"Hello World"*/);
11:  }
12: }

The // characters denote a single-line comment. You can use // on a line of their own, or they can follow a code statement:

int nMyVar = 10; // blah blah

Everything after the // on a line is considered a comment; therefore, you can also use them to comment out an entire line or part of a line of source code. This is the same kind of comment introduced in C++.

If you want a comment to span multiple lines, you must use the /* */ combination of characters. This type of comment is available in C, and is also available in C++ and C# in addition to single-line comments. Because C/C++ and C# share this multiline comment type, they also share the same caveat

Summary

In this chapter, you created, compiled, and executed your fir