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. |
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
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.
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.
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.
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.
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.
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).
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.
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 SystemSo 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 SystemThe 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." MetadataAlthough 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 SpecificationThe 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 SystemThe 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. |
SummaryIn 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# ApplicationChoosing an EditorYou 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 CodeListing 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 ApplicationBecause 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 OutputSo 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 CommentsWhen 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 |
SummaryIn this chapter, you created, compiled, and executed your fir |