Click to edit Master text styles
Second level
Third level
Fourth level
Fifth level
ACCU
Spring Conference,
Christ Church College,
Oxford,
Friday
30
th March 2001.
{ JSL }
Jagger Software
Limited
http://www.jaggersoft.com
Tel.
+44 (0) 1823 354 192
Hi, I'm Jon Jagger, a freelance software trainer,
designer, and consultant. I specialise in
curly bracket languages, hence { JSL }. In a former life I was QA Training's C++ and C product consultant. I'm an UK C++
standards panel member and a regular
contributor to the ACCU Overload journal. I'm married with three increasingly larger children. My interests
include training excellence, design, problem
solving, and monty python (which should be required knowledge for all software developers). Forget the
technical interview, just recite the parrot
sketch. I don't really know what else to
say in a short bio such as this. I'm
very very good at sleeping. And breathing. Both of which I practice a lot.
ACCU
Spring Conference,
Christ Church College,
Oxford,
Friday
30
th March 2001.
{ JSL }
Jagger Software
Limited
http://www.jaggersoft.com
Tel.
+44 (0) 1823 354 192
Hi, I'm Jon Jagger, a freelance software trainer,
designer, and consultant. I specialise in
curly bracket languages, hence { JSL }. In a former life I was QA Training's C++ and C product consultant. I'm an UK C++
standards panel member and a regular
contributor to the ACCU Overload journal. I'm married with three increasingly larger children. My interests
include training excellence, design, problem
solving, and monty python (which should be required knowledge for all software developers). Forget the
technical interview, just recite the parrot
sketch. I don't really know what else to
say in a short bio such as this. I'm
very very good at sleeping. And breathing. Both of which I practice a lot.
ACCU
Spring Conference,
Christ Church College,
Oxford,
Friday
30
th March 2001.
{ JSL }
Jagger Software
Limited
http://www.jaggersoft.com
Tel.
+44 (0) 1823 354 192
Hi, I'm Jon Jagger, a freelance software trainer,
designer, and consultant. I specialise in
curly bracket languages, hence { JSL }. In a former life I was QA Training's C++ and C product consultant. I'm an UK C++
standards panel member and a regular
contributor to the ACCU Overload journal. I'm married with three increasingly larger children. My interests
include training excellence, design, problem
solving, and monty python (which should be required knowledge for all software developers). Forget the
technical interview, just recite the parrot
sketch. I don't really know what else to
say in a short bio such as this. I'm
very very good at sleeping. And breathing. Both of which I practice a lot.
Static
typing, runtime polymorphism (virtual methods), exceptions and "reference"
types, are features also found in C#, C++ and Java.
Value types, and
operator overloading are features found in C#, C++ but not in Java. Compile
time polymorphism (templates, aka generics) is promised for a later release of
C#.
A virtual execution system (similar to the Java VM), Object as the
super base class of all types, single public inheritance (extension in Java),
multiple public realization (implements in Java), non determinstic
finalization, garbage collection, multithreading, and reflection are features
found in C# and Java but not in C++.
C# has a truly unified type system
(achieved through boxing and unboxing, covered later). C++ does not. Java
almost has a unified type system (primitive value types need to be manually
wrapped).
Unlike in
Java the name of the C# source file does not have to correspond to the name of
the public class contained in that source file.
C# is a case sensitive language. Main
is spelled with a capital M.
There are several
Main
variations borrowed from C++ and Java. You can declare
Main to return an
int,
with a return value of zero indicating success:
public static int Main() { ... return 0; }
You can declare Main as a void function:
public static void Main() { ... }
You can also declare Main
to accept an array of strings:
pubic static void Main(string[] args)
{
for (int i = 0; i != args.Length;
i++) {
System.Console.WriteLine(args[i]);
}
}
The program's Main must be a
static
method. If there is more than one public static Main in a program one of them
must be designated the entry point to the program using the
/main:<type> command line
option (where type is the name of the struct/class containing the selected
Main).
Microsofts
IL (Intermediate Language) is an important parts of the .NET VES (virtual
execution system). The virtual execution model consists
of an evaluation stack used for pushing arguments to
methods and built-in operations. At a method call the arguments (possibly including this) are transferred
from the control of the calling method to the
control of the called method. Returned
values of any instantiable type are
pushed on the stack before method return.
The
IL instruction set can be divided into the following categories of instructions:
Arithmetic-Logical Operations (includes
coercion). Optional overflow detection.
Control Flow (includes throwing and catching exceptions)
Direct Memory Access
Stack
Manipulation
Argument and Local Variables
Stack
Allocation
Object Model (support for boxing and unboxing)
Values of Instantiable Types
Critical Region
Arrays
Typed
Locations
The
important parts of the .NET VES (virtual execution system) are:
An intermediate
language (IL) that can be easily and effectively targeted by a wide range of compilers.
Loading managed
code including resolving names, performing class layout, and creating stubs needed to transition between managed
and unmanaged code.
Conversion of IL
into native code including when a method is first invoked (JIT compilation).
Verification of
the type safety of the methods written in IL as well as the integrity of the metadata describing those methods.
Services based
on stack format: garbage collection of
memory containing managed data, and
initiation, propagation, and intercepting exceptions.
Insertion and
management of security tests based on code identity, type safety, and administrative controls.
Profiling and
debugging services, including mapping native code instruction addresses back to IL addresses and lengthening the
lifetime of local variables.
Management of
threads, contexts, and remoting.
The
purpose of this slide (written by a programmer with a soft Irish accent) is to
present a code fragment that is dissected in the following slides.
The C#
grammar distinguishes between a number of different tokens: identifiers, keywords,
literals, operators and punctuators. Note that while C# does have a C/C++ like
preprocessor, the #define directive is strictly limited to defining a simple
token and cannot define a replacement set of tokens. This effectively means
that preprocessing tokens do not exist. Which is nice.
When generics
(templates) are added to C# it is likely they will use the
< > notation familiar to C++
programmers. It is also likely that C# will create a special exception to the
maximal-much rule allowing
>>
to be parsed in context:
int x = 42 >> 4; // bitshift
wibble<wibble<int>> variable; // declaration
C# has a
limited #define directive. You can use it to specify that the named pp-token
is defined. You cannot #define
true
or
false (this is because they
can be used as
bool literals
inside a pp-expression). You can also use #undef to specify that the named
pp-token is no longer defined. It is not an error if the specified pp-token is
not already defined. There are no function macros, so both these are
errors:
#define MACRO(exp)
do exp; while(0)
#define MACRO(exp)
#defined pp-tokens are only useful as part of the expression used to select or
skip sections of code (skipped sections do not have to be syntactically well
formed).
#if TESTED && INSPECTED
...
#endif
The #line directive is useful for tools that generate C# code. There are no
built-in __FILE__ or __LINE__ tokens in C#.
The #error and #warning
directives both emit a supplied pp-message diagnostic. A #error is fatal and
halts the build, a #warning is non-fatal and does not halt the build.
The
#region and #endregion directives are used in pairs and can be nested. They have
no declarative meaning. The editor in Visual Studio 7 uses them to create a folding
editor.
The SDK
guidelines recommend that public identifiers start with an uppercase letter
using the PascalCase convertion and that private identifiers start with a lowercase
letter using the camelCase convention. For example:
class Point
{
...
public int X() { return x; }
public int Y() { return y; }
private int x, y;
}
In C# an identifier in a derived class can completely hide an identifier in a
base class. Because of this you are strongly advised to follow this guideline.
Note also that a CLS compliant program must not use public identifiers
that differ only in case. For example:
[CLSCompliant(true)] // attribute
...
public void Name() {...}
public void name() {...} // error CS3005
The
keywords in the above slide are coloured in blue. For those of you reading this
in monochrome the keywords are using,
public, class, static, void,
int.
Keywords have a fixed meaning. Part of learning a new language
is learning the meaning of the keywords. Another more exciting part is
learning how to create larger syntactic elements once the basic syntax has
been grasped. But you have to start at the beginning. The time and effort
expended learning the keywords and their meanings is well spent, especially as
the keywords are fixed; they will not change (unlike just about everything
else in computing!).
In Beta 1
there are 76 keywords that have a fixed meaning in all contexts. There are
also a few 2
nd-class keywords that have a fixed meaning only in
certain special contexts. For example,
value
can be used as the name of a local variable almost everywhere. In one context
(the
set accessor of a
property)
value is
effectively a keyword.
You are allowed to use @ before a
keyword to create a legal identifier.
For example:
int @int = 42;
This is not recommended and is presumably included for the benefit of code generation
tools.
You can also use the @ symbol to create verbatim-string literals
that span many lines. This is useful when creating regular expressions for use
with the Regex class. For example:
string pattern = @"
( # start
the group
abra(cad)? # match
abra and optional cad
)+"; #
one or more occurences
Only the
built-in types have literal syntax. There is no way to create literals for user-defined
structs or classes.
Literals are pure values. They are constants. They cannot be modified.
6++; // compile time error
Just like in Java, a C# string literal is a constant immutable value of
type string.
C# has a
rich set of operators making it a very expressive langauge. All the operators
in the table above are present in C#, C++, and Java. In C# all the above operators
(except simple assignment) can be overloaded for user defined structs and
classes. This is not possible in Java.
A C#
class/struct/enum definition does not require a terminating semi-colon (like Java).
public class Hiker
{
...
} // no ; OK
However, as a concession to C++ programmers, a semi-colon may be used and has
no affect on the meaning of the program:
public class Hiker
{
...
}; // also OK
In Java a function declaration can have a trailing semi-colon. In C++ it
cannot (but many compilers allow it). In C# it is not allowed and causes a
compile time error:
public class Hiker
{
public void Hitch() { ... }; // ; not
OK
}
It is
illegal to declare a variable in a context where that variable would be unusable.
For example:
if (...)
int x = 42; //
compile-time error
else
...
Declarations and expressions can be freely mixed. Most modern C++/Java style guidelines
recommend delaying the declaration of a variable to increase the chance of
being able to initialize it. This is important because C#, like Java, will not
allow the value of a not-definitely assigned variable to be used.
There
are no pointers or typedefs in C#. Hence troublesome C++ declarations, such
as:
int *ptr, value; // C/C++
are no longer an issue. A useful style is to
declare multiple variables in the same declaration when the variables are
necessarily
of the same type:
int x, y;
A C#
expression statement must have an affect on the program state. This means that
only assignment, call, increment, decrement, and new expressions can be used
as a statement (this is true for built in types and user-defined types):
42; //
compile-time error
42 == 42; // compile-time error
However, initialising a variable in a C# declaration statement is deemed a
side effect: Hence the following are legal and at best generate a
warning:
{ int m = 42; }
{ bool b = 42 == 42; }
You may be
looking at this slide and wondering where the built-in types such as int and
double are. The answer is that in C# the built-in value types are structs in disguise
called simple types. The main difference between simple struct types and
user-defined struct types is that the former have a literal syntax (such as 42
which is an int) whereas the latter do not.
There is also a third type
category: pointers, but pointers can only be used in C# code declared as unsafe.
The first
code box declares and intializes two bool variables called love and teeth.
The
second code box is identical to the first code box. System.Boolean is an alias
for bool. They are synonyms.
The third code box also uses the identifier
Boolean as a synonym for bool. It does this using the shorthand notation of
dropping the System. prefix which the compiler implicitly adds back in due to
the using System; directive.
The code in the third box generates an error.
There is no global type called Boolean and there is no using System; directive
so the compiler cannot resolve Boolean as the name of a type.
System.Boolean
is a struct that lives inside the System namespace. It realizes a number of
interfaces (structs and inheritance are fully covered later):
public struct Boolean : IComparable, IConvertible
{
...
}
The value of System.Boolean.FalseString is "False" with a
captial F.
The value of System.Boolean.TrueString is "True" with a capital
T.
Instance methods of System.Boolean can be called directly on bool
literals:
true.ToString();
Static methods of System.Boolean can be called using bool as an alias for System.Boolean:
bool.Parse(Console.ReadLine());
Signed
numbers and byte are in the
Common Language Specification (CLS). ushort,
uint, and ulong are not in CLS. The types that
are part of the CLS form an integral [sic] part of the CTS (common type
system) are are supported directly by the VES (virtual execution system).
Note: a CLS compliant program cannot expose unsigned types in the public interface
of a struct/interface/class but a CLS compliant program can use unsigned types
in the private parts of a struct/class.
The bitsizes of each type are
precisely specified just like in Java (and unlike in C++).
In code you can
use the raw keyword type name (eg int)
or its equivalent alias (eg System.Int32). There is no difference (except in
runtime reflection when you have to use the string "System.Int32"
rather than the string "int"). The guideline is to prefer the
keyword.
Note that a byte in C# is an unsigned 8 bit integer whereas in
Java a byte is a signed 8 bit integer.
The names of these 8 integral types
in IL are intN (where N is the number of bits) for the signed types and
unsigned intN, for the unsigned types.
The byte and sbyte bit sizes
are marked with an asterisk. This is because the minimum size of an array
element is 16 bits (2 bytes).
System.Int32
is a
struct that lives inside
the
System namespace. It
realizes a number of interfaces (structs and inheritance are fully covered
later):
public struct Int32
: IComparable, IFormattable,
IConvertible
{
...
}
IFormattable is an interface that allows user-defined types to define their
own format strings (in exactly the same whay that printf doesn't).
The
value of System.Int32.MinValue is unchecked ((int)0x80000000), that is, -2,147,483,648,
== -(2^32)
The value of System.Int32.MaxValue is 0x7FFFFFFF, that is,
+2,147,483,647, == 2^32 1
int
is a synonym for System.Int32. They can be used completely interchangeably.
System.Int32.ToString(42) is a call to the ToString static
function
int.ToString(42) is an identical call to the same ToString
static function
42.ToString() is
a call to the ToString instance method (calling a function on a literal like this is not possible in Java).
The syntax
for integer literals is the same in C# as it is in C++ and Java except for two
minor differences: firstly, C# does not support octal integer literals; C++ and
Java do. Secondly, C# supports unsigned integer literals (via the Uu suffix); Java
does not.
The keyword int
is an alias for the System.Int32 struct. In C# int
is classified as a simple-type and differs from other user-defined
structs in a number of ways:
Most simple types have a syntax for creating literal
values. For example 42 is a literal of type int,
23.11 is a literal of type double.
C# offers no facility for creating literals of user-defined struct (or class) types. The values of user-defined structs (or
classes) are usually created through constructors.
When the operands of an expression are all simple-type
literals, the compiler can evaluate the expression as a compile-time
expression. When the operands of an expression involve user-defined struct
values, the expression will always be a runtime-expression.
You can declare immutable variables
of simple types using the const
keyword. You cannot declare immutable variables of user-defined structs
(or classes) using the const
keyword.
As a matter of style, the C# language specification recommends
using L instead of l when writing literals of type long since it is easy to confuse the letter l with the
digit 1. In fact using lowercase l generates a warning. Why didn't they just enforce
the use of uppercase suffixes?
C# defines
the usual range of integer operators.
These are the primary C# operators which all share the highest
precedence.
The increment and decrement operators are overloadable for custom types.
The pointer member access operator -> can only be used in unsafe
(unmanaged) code.
Strictly speaking an indexer is not considered an
operator (there are some language rules this affects). Indexers can be defined
for custom types.
new is only ever used in conjunction with calling a
constructor and is not overloadable. Using new to call a
struct constructor allocates memory
from the stack (honest) whereas using new to call a
class constructor allocates memory from the heap. In C#
structs are value types whereas classes are reference types.
sizeof
returns the size of a type or expression and can only be used in code marked
as unsafe.
checked and unchecked are used to control detection of integer
arithmetic overflow (covered shortly).
All the
unary operators can be overloaded for user-defined types. The type cast operator
can be overloaded by creating implicit or explicit conversion operators.
The
multiplicative, additive, shift, relational (except for is and as), equality,
and bitwise operators can also be overload for user-defined types.
The
&& and || can sort of be overloaded by using true/false conversion
operators (which retain the short-circuiting behaviour). Very strange.
The
conditional (?:) and the assignment operators cannot be overloaded. However,
a compound assignment is effected in terms of its underlying operator which
can be overloaded.
This precedence table is essentially identical to that
of C++ except that C++ has more operators. It is instructive to note the C++
operators that do not appear in the above table:
scope resolution
:: (C# uses a dot like Java)
dynamic_cast
(called is in C# with lower precedence)
static_cast (use
(all casts are checked in C#)
reinterpret_cast
(C# won't let you do anything remotely dangerous)
const_cast (C#
does not support const except for static data),
delete (C# has
non determinstic finalization)
.* and ->*
(C# uses delegates)
throw (C# treats
throw as a statement)
comma (not
present in C# except in for statement).
The
associativity rules in C# are the same as in C++ and Java (which in turn were chosen
for compatibility with C). Most expressions can be written naturally without
parentheses. However, there are a few common idioms which require parentheses.
For example:
//class cast combined with member
access
((System.Int32)boxed).Int32();
// assignment combined with
comparison
while ((line = src.ReadLine()) !=
null) { ... }
By default
compile time integer expressions generate an OverflowException and
runtime
integer expressions silently overflow. However this default behaviour is configurable.
There is a command line option /
checked+
to turn on overflow checking and similarly /
checked- to turn off overflow checking.
When
overflow checking is on an expression that would normally silently wrap throws
an OverflowException. You can also use the
checked/
unchecked operators to control
overflow checking:
checked
expressions/statements are
always checked,
unchecked expressions/statements are
never
checked.
A checked ( expression ) checks a single expression. It can be
used on any expression but only affects the integer operators that can cause
overflow: unary { ++,
--, - },
binary { + - * / }, and explicit conversion operators between integral types.
A checked expression is an expression; it has an outcome that can be used as
part of a larger expression:
int outcome = checked( ... );
A checked { statement } checks a sequence of statements. A checked statement does
not have an outcome; it is not an expression.
int outcome = checked
{ ... }; // compile-time error
The
checked and unchecked operators only affect the overflow checking context of
the operations they "textually" contain. They have no effect on
textually contained function member invocations.
Division by zero always throws a DivideByZeroException.
If the
left hand operand is int or uint (32 bit types) the shift distance is always
in the range 0 to 31 inclusive.
Only
the five lowest-order bits of the right hand operand are used as the shift
distance.
n << s ΰ n << (s & 0x1F)
If the left hand operand is long or ulong (64 bit types) the shift distance is
always in the range 0 to 63 inclusive. Only the six lowest-order bits of the
right hand operand are used as the shift distance.
n << s ΰ n << (s & 0x3F)
The integer shift operators never throw exceptions. You can overload the shift
operators for user-defined structs/classes. There is no
>>> operator as in Java, since
C# has signed and unsigned types, (although the unsigned types are not part of
the Common Language Specification CLS).
There are no shift operators for
shifting
bytes or
shorts. Shifting a byte/short will
cause the byte/short to be converted to an
int.
This means the return type will be an
int.
This means, given b is a byte, the following will not compile:
b = b << 1; // can't assign an int to a byte
To solve this problem you can use an explicit conversion (a cast) or use the compound
assignment form of the shift operator:
b = (byte)(b << 1);
b <<= 1;
Integer to
integer conversions are performed using explicit conversion
operators (in
other words; casts). Casts can be used in a checked expression:
int m = int.Parse(Console.ReadLine());
short s;
s = checked((short)m);
// 1
s = unchecked((short)m); // 2
s = (short)m; //
3
Statement 1
is always checked and if the value of m is outside the range of a
short an
OverflowException is throw.
Statememt 2 is never checked and never causes an exception to be
thrown.
Statement 3 may or may not be checked (depending on the optional use of the
/checked+ compilation flag).
The
top left table in the slide is the same in Java (except in Java the unsigned 8
bit integer is called byte and not sbyte)
C#
supports two floating point types:
float
and
double. There is no
long double
as in C/C++. Both types conform the the IEEE 754 formats:
In C#, C++, and Java the suffix for float is F or f. In C#, as in Java, the
suffix for double is D or d. In C++ there is no explicit suffix for doubles
other than L or l for the long double type (which does not exist in C# or
Java).
As well as System.Single and System.Double there are other
varieties of cream classes in the System namespace: Whipping, Pouring,
Clotted, and Ice to name but four.
More information on IEEE-754 can be found at: http://www.ieee.org
Positive and
negative zero.
Positive and
negative infinity. Infinities are produces by operations such as dividing a non-zero number by zero.
The Not a Number
value, often abbreviated to NaN. NaN's are produced by invalid operations such as dividing zero by
zero.
The finite set
of values of the form
Sign x Mantissa x 2Ecponent
where Sign is 1 or 1 and Mantissa and
Exponent are determined by
the type.
The
keyword
float is an alias for
the
System.Single struct type
and the keyword
double is an
alias for the
System.Double struct
type. This means that when you write:
double d;
you are actually writing:
System.Double d;
If you use a
using directive to
make all types from the
System
namespace available you do not need to explicitly name the System prefix:
using System;
...
double d1;
Double d2;
There are a few typename keywords that share this property of only differing
in the first letter: object
ΰ System.Object, string
ΰ System.String. You are recommended to use the
keyword typename.
The M or m suffix is used for decimal literals (covered
shortly).
The literal
234F (which does
not
contain a decimal point) is legal in C#
and in Java but not in C or C++. Similarly the literal
234.F (which
does contain a decimal
point) is not legal in C# but is legal in C, C++, and Java.
The fact that
234.F is not legal C# is due to
potential grammatical ambiguities. Consider;
234
is a literal of type
int and
int which is an alias for System.Int32
which is a
struct that declares
fields and methods. Suppose System.Int32 declared an instance field called F.
The syntax
234.F would then be
ambiguous: is it a double literal with a type suffix or the F field being accessed
through an
int value? The
compiler thinks the latter:
float f = 123.F; // compile time error. No
F in 'int'
string s = 123..ToString(); // compile time error
Note however that the following are legal:
double d = 123.0D;
string s = 123.0.ToString();
There appears to be no way in C# to initialise a floating point literal other
than in decimal radix (in Java you can convert a IEEE bit pattern contained in
a
long into a
double using the
Double.longBitsToDouble method.
You can
use the % operator on floating point operands (you can in Java too, but not in
C++). You cannot use the shift operators on floating point operands (you can't
in Java or C++ either).
Floating point operators never throw exceptions
(this includes the assignment operator remember the definite assignment
rule). Instead in exceptional [sic] circumstances floating point operations
produce zero, infinity, or NaN, as described below:
If the result of
a floating point operation is too small for the destination format, the result of the operation becomes positive or
negative zero.
If the result of
a floating point operation is too large for the destination format, the result of the operation becomes positive or
negative infinity.
If a floating
point operation is invalid, the result of the operation becomes a NaN.
If one or both
operands of a floating-point operation is NaN, the result of the operation becomes NaN.
Converting a double to a float requires a
cast. The double value is first rounded to the nearest float value. If the
double value is too small to represent as a float, the result becomes positive
zero or negative zero. If the double value is too large to represent as a
float, the result becomes positive infinity or negative infinity. If the
double value is NaN the result is also NaN.
An integer to floating point conversion is always implicit and
never requires a cast (although one may be used if desired). Conversions from
int, uint, or long to float and from long to double may cause a loss of
precision, but will never cause a loss of magnitude. The other implicit
integer to floating point conversions never lose any information.
Conversion from a float or double to an
integer type require an explicit cast. The source value is rounded to the
nearest integral value, and this integral value becomes the result of the
conversion. If the resulting integral value is outside the range of the
destination type, an OverflowException is thrown. At least, that is the
documented behaviour in the language specification. As at Beta 2 whether an OverflowException
is actually thrown depends on the overflow checking context:
double d =
double.Parse(Console.ReadLine());
int m;
m = checked((int)d); // 1 may throw
m = unchecked((int)d); // 2 never
throws
m = (int)d; // 3 depends on context
The C#
language specification states that "The decimal type is a 128-bit data
type suitable for financial calculations". A decimal is represented as a
96-bit integer scaled by a power of ten.
The decimal type holds 28 digits
and the position of the decimal point in those digits. The decimal type is
useful for financial calculations; it has high precision and can store base 10
numbers (as you might imagine for a type called decimal!) The number 0.1 is
stored exactly in a
decimal but
inexactly as a recurring binary in a floating point type (
float or
double).
The floating-point suffix for the decimal type is M or m (m for
money?).
decimal value = 3552566.23M;
The decimal type is 128 bits, and since a C# byte is always 8 bits long,
decimal is 16 bytes long (despsite what every other C# book says!)
The
decimal keyword is an alias for
System.Decimal, a struct that lives inside the System namespace. It realizes a
number of interfaces (structs and inheritance are fully covered later):
public struct Decimal
: IConvertible, IComparable, IFormattable
{
...
}
sizeof(decimal) is classified as a value and not a constant. This is despite
the fact that the C# language specification clearly states that decimal is a
128 bit type.
When converting a float or double to a
decimal, the source value is converted into a decimal representation and
rounded to the nearest number after the 28
th decimal place if required. If the source
value is too small to represent as a decimal, the result becomes zero. If the
source is NaN, infinity, or too large to represent as a decimal, an
InvalidCastException is thrown.
When converting a decimal to a float or double, the decimal value
is rounded to the nearest float or double value. This conversion may lose
precision (since the decimal type has greater precision) but never throws an
exception.
When convering a
decimal to an integral value, the source value is rounded towards zero to the
nearest integral value, and this value becomes the result of the conversion.
If the resulting integral value is outside the range of the destination type
an OverflowException
is thrown.
An integral to decimal conversion is an implicit conversion
because the decimal type can represent every possible integer value.
There is no concept of +0, -0, +infinity, -infinity, or NaN for the decimal type.
Note that the double to decimal conversion is not implicit. This means that
you cannot initialize or assign a decimal from an unadorned floating-point
literal:
decimal failure = 3552566.23; // compile time error
decimal success = 3552566.23M; // OK
The char
type represents an unsigned 16-bit integer with values between 0 and 65535.
The set of possible values for the char type corresponds to the Unicode character
set.
Unicode consortium.
The Unicode Standard, Version 3.0.
Addison
Wesley, Reading, Massachusetts,
2000.
ISBN 0-201-616335-5
System.Char
is a struct that lives inside the System namespace. It realizes a number of
interfaces (structs and inheritance are fully covered later):
public struct Char
: IComparable, IConvertible
{
...
}
A new-line
terminal is defined as: a carriage return character (U+000D), a line feed character
(U+000A), a carriage return character (U+000D) followed by line feed character
(U+000A), a line separator character (U+2028), or a paragraph separator character
(U+2029).
In C# (as in Java) a character literal contains the
representation of a single character (not true in C++!).
A character that
follows a backslash character must be one of the designated characters
otherwise a compile time error occurs.
0x000B
vertical tab
\v
0x0009
horizontal tab
\t
0x000D
carriage return
\r
0x000A
new line
\n
0x000C
form feed
\f
0x0008
backspace
\b
0x0007
alert
\a
0x0000
null
\0
0x005C
backslash
\\
0x0022
double quote
\"
0x0027
single quote
\'
There are
no implicit conversions from other types to the
char
type. In particular, even though the
sbyte,
byte, and
short types have ranges that are fully
representable using the
char
type, implicit conversions from
sbyte,
byte, or
short to
char do
not exist.
char exists mainly to be the element type of string.
An enum declaration may appear in the same places as a class declaration.
An enum declaration defines the name, accessibility, underlying type, and members
of the enum. The underlying type must be able to hold all the values of the
named constants defined in the enum.
The named constants defined in the
enum are scoped inside the enum. In other words the following is not
well-formed:
Suit trumps = Clubs; // compile time error
Clubs must be scoped as a member of Suit:
Suit trumps = Suit.Clubs; // OK
Java does not have enums (not to be confused with Enumeration which is an interface).
In Java enums have to be faked up using static readonly fields:
class Suit
{
public static readonly Club = ...;
}
C++ does have enums but the named constants do not require the name of the enum
as a prefix.
enum suit { clubs, diamonds, hearts, spades };
suit trumps = clubs; // OK in C++
Uniquely,
a C# an enum type may explicitly declare its underlying type as sbyte, byte,
short, ushort, int, uint, long, or ulong. An enum declaration that does not explicitly
declare its underlying type defaults to an implicit underlying type of int.
Named constants have the same type as the underlying type and must have a
value in the range of the underlying type (for example, you cannot create a named
constant with a negative value if the underlying type is explicitly declared as
uint). If the named constant is not given an explicit constant value it
defaults to the value of the previous named constant plus one (and the default
value of the first named constant is zero). It is not an error for more than
one named constant in the same enum declaration to have the same value. The
initializer for a named constant cannot include itself either directly or
indirectly and cannot refer to a following named constant (this would create a
circular dependency and is one of the few cases of an order dependency in C#).
The last named constant can be declared with a trailing comma. This makes
it easy to add more named constants in the future, although if this is likely
you should consider not using an enum! An enum is best suited to a
fixed
number of grouped
names: you are extremely unlikely to need a fifth
suit, or a thirteenth month, etc. Try and avoid explicitly initializing named
constants.
Enum members (such as Club) implicitly have public declared
accessibility.
As a gesture to C++ programmers, an enum declaration is allowed an optional trailing
semi-colon (it is in Java too). It's best to get out of the habit using a trailing
semi-colon as it not perimtted on a function definition (it is in Java).
System.Enum
is a
class that lives inside the System namespace. It realizes a number
of interfaces (classes and inheritance are fully covered later):
public class Enum
: IComparable, IFormattable,
IConvertible
{ ... }
enum types (such as Suit) implicitly
derive from System.Enum (as well
as implicitly or explicitly deriving from their underlying type). Note that
this is different to types as the System.Int32
struct
which is an
alias for
int.
C# has powerful introspection capabilities (introspection is the ability
of a program to observe and reason about its own state). Unlike C++, C#
can easily print out the name of a enum
variable. You can do this via the IFormattable interface: directly by calling
the Format method or indirectly by using the "{0}" format string.
Note that using the ToString method prints the value of the enum variable and
not its name.
System.Enum is a special class designed to be used only as
an implicit base class of all enum types.
you cannot explicit derive from System.Enum
yourself (error CS0644)
you cannot create an instance of System.Enum
(constructor is protected)
However, you can declare a (reference) variable of type
System.Enum
if value
does not correspond to a named constant Format prints the value.
ValueType
is a class that lives in the System namespace. This class the root class of
all value types (and implicitly derives from the System.Object class). Via a little
introspection you can prove that the value types derive from System.ValueType,
and that arrays and classes do not.
int m = 42;
Suit trumps = Suit.Club; // Suit is an enum
Pair p = new Pair();
// Pair is a struct
int[] array = new int[42];
ValueType v = new ValueType();
Console.WriteLine(m.GetType().BaseType);
Console.WriteLine(trumps.GetType().BaseType);
Console.WriteLine(p.GetType().BaseType);
Console.WriteLine(array.GetType().BaseType);
Console.WriteLine(v.GetType().BaseType);
The five WriteLine statements print System.ValueType (three times), then System.Array,
and then System.Object.
A
parameter declared without a
ref or
out modifier is a
value
parameter. A value parameter only comes into existence upon invocation of the
function member to which the parameter belongs, and is initialized with the
value of the argument given in the invocation. A value parameter ceases to
exist when the flow of control leaves the function member (either via normal
return or a thrown exception).
For the purposes of definite assignment
checking, a value parameter is considered initially assigned. That is, once
the invocation takes place the value parameter is considered definitely
assigned. However, only a definitely assigned argument can be passed as a
value parameter.
int m; // m not
definitely assigned
Func(m); // compile time error
The argument corresponding to a value parameter can be a pure value and need not
be a named variable:
Func(42);
Func(Suit.Club);
Func(new Pair());
A
parameter declared with a
ref
modifier is a
reference parameter. A reference parameter does
not
create a new storage location. Instead the reference parameter is an
alias
for the variable given as the argument in the function member invocation. In
effect a reference parameter is another name for an existing variable.
Note
that the
ref modifier is
required on both the function member declaration and on the function member
invocation.
For the purposes of definite assignment checking, a reference
parameter is considered initially assigned. That is, once the invocation takes
place the reference parameter is considered definitely assigned. However, only
a definitely assigned argument can be passed as a reference parameter.
int m;
// m not definitely assigned
Func(ref m); //
compile time error
The argument corresponding to a reference parameter must be a modifiable variable
(an lvalue); it cannot be a pure value or a const variable:
Func(ref 42); //
compile time error
const int m = 42;
Func(ref m); //
compile time error
A
parameter declared with an
out
modifier is a
output parameter. An output parameter does
not
create a new storage location. Instead the output parameter is an
alias
for the variable given as the argument in the function member invocation. In
effect an output parameter is another name for an existing variable.
Note
that the
out modifier is
required on both the function member declaration and on the function member
invocation.
For the purposes of definite assignment checking, an output
parameter is considered initially unassigned. An output parameter must be
definitely assigned at the end of its function.
int m;
// m not definitely assigned
Func(out m); //
OK, m now assigned
The argument corresponding to a reference parameter must be a modifiable variable
(an lvalue); it cannot be a pure value or a const variable:
Func(out 42); //
compile time error
const int m = 42;
Func(out m); //
compile time error
In C#, and
Java (but not C++) a function that expects no parameters must be declared with
empty parentheses:
int F(void)... // illegal in C# and Java
The
ref and
out modifiers form part of the
signature of a function. This means you can overload based on them (remember,
the ref and out modifier must appear in the call). The ref and out modifiers
are safe in the sense that only a ref argument can be bound to a ref
parameter, only an out argument can be bound to an out parameter. However, it
would be very easy to forget a ref or out modifier at the call site so it is
best to avoid ref/out overloading.
In C#, as in Java, you are not allowed
to omit the name of a parameter. In C++ you are allowed to omit the name of
the parameter in a function declaration and a function definition.
int F(int /*noname*/)... // illegal in C# and Java
In C#, as in Java, there are no default arguments (there are in C++). This is
no great loss since in both C# and Java a constructor can call a sibling
constructor. And default arguments are widely regarded as a bad idea in C++
anyway.
In C#, as in Java, there is no way to specify a
"readonly" parameter (other than value parameters for value types).
There is in C++ (via const T * or const T &).
C# in common with Java (but not C++) does not allow a cast to void:
(void)Method(); // illegal in C# and Java
In C#, and
Java (but not C++) a function that expects no parameters must be declared with
empty parentheses:
int F(void)... // illegal in C# and Java
The
ref and
out modifiers form part of the
signature of a function. This means you can overload based on them (remember,
the ref and out modifier must appear in the call). The ref and out modifiers
are safe in the sense that only a ref argument can be bound to a ref
parameter, only an out argument can be bound to an out parameter. However, it
would be very easy to forget a ref or out modifier at the call site so it is
best to avoid ref/out overloading.
In C#, as in Java, you are not allowed
to omit the name of a parameter. In C++ you are allowed to omit the name of
the parameter in a function declaration and a function definition.
int F(int /*noname*/)... // illegal in C# and Java
In C#, as in Java, there are no default arguments (there are in C++). This is
no great loss since in both C# and Java a constructor can call a sibling
constructor. And default arguments are widely regarded as a bad idea in C++
anyway.
In C#, as in Java, there is no way to specify a
"readonly" parameter (other than value parameters for value types).
There is in C++ (via const T * or const T &).
C# in common with Java (but not C++) does not allow a cast to void:
(void)Method(); // illegal in C# and Java
As in C++
and Java, the return type is not considered part of the signature and cannot
be used as basis for overloading. Note also that there is no return type covariance
in C#.
Like Java, C# does not allow global functions. All functions must
be in the scope a class or struct; they must be function members.
C#, like
Java, has no concept of header files. All function declarations exist inline
in the class/struct declaration.
C#, like Java, allows an optional
trailing semi-colon on a class declaration. However, C#, does not allow a
trailing semi-colon on a function declaration (Java does) :
There are no throw specifications in C#. This is a major difference to
Java.
C# shares with Java (but not with C++)
the rule that a block and its parent block cannot have a variable with the
same name.
All three languages allow a block to
declare a local variable with the same name as an in scope field.
All three languages do not allow a
parameter to be redeclared as a local variable or an exception
parameter:
void Method(int parameter)
{
int parameter; // compile time error
}
void Method(int parameter)
{
try {
...
}
// compile time error
catch (Exception parameter) {
...
}
}
In C# (as
in Java, but unlike C++) there are no conversions (implicit or explicit) to or
from bool. Ever. Nada. Nothing. Zip. This means that the expression inside the
parentheses must be a genuine boolean expression.
The definite
assignmentrule of C# (and Java) understands the selection point introduced by
an
if statement (and the ?:
ternary operator). A variable is only considered definitely assigned if all
paths through the code (via
static flow analysis) definitely assign to
the variable. For example, the following generates a compile time error
regardless of the contents of Function (which must return a
bool):
int m;
if (Function())
m = 42;
int copy = m; // error: m not definitely
assigned
In C#, (again as in Java) the statement controlled by the
if is an embedded-statement, which
cannot be a declaration-statement. In other words, the following is illegal in
C# and Java:
if (condition())
int useless; // declaration
statement
...
C#, (unlike C++) does not allow a variable to be declared as part of the
controlling boolean expression:
if (bool married = !bachelor(boy)) ...
definite assignment
ability to reorder clauses
You can
only switch on a user-defined type if that that type has a single implicit conversion
to an integral type (including enum) or string.
no bool
conversions
Reason for
preferring != to < in the boolean continuation expression.
The C#
foreach statement works on any collection that implements the IEnumerable
interface which lives in the System.Collections namespace. Arrays implicity
derive from the System.Array class which supports this interface meaning that
a foreach statement can iterate through an array.
The IEnumerable interface contains one operation:
public interface IEnumerable
{
IEnumerator GetEnumerator();
}
IEnumerator
is also an interface which
lives in the System.Collections namespace. It provides a standard mechanism
for iterating over a collection:
public interface IEnumerator
{
bool MoveNext();
object Current { get; }
void Reset();
}
Current is a readonly property. Properties are fully covered later.
The foreach expression can also be of a type deemed to be a "collection
type". A collection type is defined by the following pattern:
C
contains a
public instance
method with the signature
GetEnumerator()
that returns a
struct-type,
class-type, or
interface-type,
which is called
E in the following
text.
E contains a
public instance method with the
signature
MoveNext() and
the return type
bool.
E contains a
public
instance property named
Current
that permits reading the current value. The type of this property is said to
be the
element type of the collection type.
rules of
finally interaction with goto, break, continue: 8.9.1 and 8.9.2 and 8.9.3
As a
gesture to C++ programmers a struct declaration is allowed a trailing semi-colon.
In fact, a Java class definition is allowed one too. It is best to get used to
not using the trailing semi-colon since C# function members are not allowed a trailing
semi-colon (they are in Java):
class Example
{
// trailing ; illegal in C#, OK in Java
public void Function( ) { } ;
}
The basic
syntax for declaring a C# constructor is the same as in Java and C++. The main
difference is that C# constructors cannot declare a throw specification (since
there are no throw specifications in C#).
A
struct
will always have a compiler generated default constructor. If you don't
declare a constructor for your struct the compiler generated default constructor
exists. If you do declare a constructor for your struct the compiler generated
default constructor still exists. This means that you cannot declare a user-defined
default constructor for a struct (since then there would be two). Note that
this rule does not apply to classes. The compiler generated default constructor
zero initializes all instance fields of the object to zero:
bool
to false
integral types (including char) to 0
floating point types(including decimal) to 0.0
enum
types to 0 (this is a legal implicit cast)
references types (which includes string) to null
Note that the default accessibility of a struct and class constructor is
private (as it is for fields). This is different to Java where the
accessibility of the default constructor depends on the accessibility of the
containing class.
Note that in Java you can override the default
constructor, and if you declare any constructor the compiler does not then
generate the default constructor. Java also allows a method with the same name
as a constructor (C# does not).
In C# a
constructor can call a sibling constructor as shown on the slide. Note that in
Java the syntax is subtley different; the chained call occurs inside the {
body }
// Java code
public Point(int x, int y)
{
this(x, y, Colour.Red);
}
The general member-initialization-list syntax available in C++ is not legal
syntax in C#.
// C++ code
Point(int initial_x, int initial_y)
: x(initial_x), y(initial_y)
{
}
There are no issues regarding a
struct
constructor calling a base class constructor since a
struct cannot
inherit
from a class.
In a struct
constructor this acts as an out parameter bound to the object being
constructed. struct
constructors must explicitly initialise all instance fields (these rules do
not apply to class
constructors).
public BadPair(int x, int y)
{
this.x = x; // fails to initialise
this.y
}
private int x, y;
Instance fields of a struct can only be initialized in a
constructor; they cannot be initialized using the variable initialisation
syntax (again this is not true for class
constructors):
private int x;
private int y = 0; // illegal in a
struct
In Java you can initialize a field in
a "free block", for example:
private Hiker arthur; // note
semi-colon
{
arthur = new Hiker();
}
This is not legal syntax in C#.
...
The value of a static readonly field
is dynamically loaded by the IL whereas the value of a const is statically
loaded and is part of the IL itself. For example, consider the following code
fragment:
class
Life
{
public static readonly question = 9 *
6;
public const int answer = 42;
static void Main()
{
Console.WriteLine(Life.question);
Console.WriteLine(Life.answer);
}
}
The IL generated from Main is as
follows:
ldsfld int32 Life::question
call void
[mscorlib]System.Console::Write(int32)
ldc.i4.s
42
call void
[mscorlib]System.Console::Write(int32)
In C# a
const field is implicit
static. In Java a
final field is not implicitly static.
The first error is caused by attempting to initialize a const field in a
constructor. A const field can only be initialized using the variable
intialization syntax:
The second error is caused by specifying the const
int field with an explicit static modifier. A const field is implicitly static
and can only be implicitly static.
const int answer = 42; // now OK
The third error is caused by failing to initialize the const field (which was erroneously
attempted in the constructor).
const int question = 9 * 6; // now OK
The fourth error is caused by attempting to qualify a field of type Pair with
the const modifier. A const modifier can only be applied to simple types (the
types with a keyword name and a literal syntax), enums, and strings. Pair
cannot be any of these types since an enum cannot be initialized using
new (the only value type that can't)
and Pair cannot be an alias for a simple type or string since there are no
typedef's in C#.
Note that const fields are not versioned whereas static readonly fields
are.
A field
declared with the
static
modifier is called a static variable. A static variable comes into existence
when the type in which it is declared is loaded, and ceases to exist when the
program terminates.
The initial value of a static
variable is the default value of the variable's type:
all fields zero for a struct type
null
for reference types
Remember, the
static modifier
cannot be applied to enum types or simple types. It can only be applied to
user-defined struct types and class types. Note that since string is a class
type (a reference type) it is possible to create a constant string using
either syntax:
const string firstName = "Arthur";
static readonly string secondName = "Dent";
string is the only type with this distinction.
Like Java, static fields can only be accessed using the name of the enclosing
type (in C++ you can use the typename or a variable).
In Java
you can write code a static initialisation block (or two or three...) in a class
declaration and that code will be executed once when the class is loaded.
There is no equivalent of Java static
initialisation blocks in C#. However, you can declare a single static
constructor which serves the same purpose.
A static constructor is called
by the .NET VES and not by a programmer. This ensures that it is called, it is
called only once, and it is called at the right time (before any use of the
struct/class). Since the programmer does not call the static constructor the
static constructor is not allowed any parameters. For the same reason the
static constructor is not allowed to be qualified with a public, protected,
private, or internal access modifier.
why use a static constructor instead
of a variable initializer? possibly to surround it with try block? would that
work?
plain
bitwise copy will never throw an exception
similarly
you cannot change the behaviour that assignment does a simple bitwise copy.
Again plain bitwise assignment will never throw an exception.
The true
and false operators are rather bizarre. They can be used to allow && and
|| to be overloaded for user-defined types whilst retaining short circuit semantics.
All types
referenced in an operator declaration must be at least as accessible as the
operator itself. Although an operator must be declared public, it may be inside
a struct/class with restricted accessibility (covered fully later).
A few
operators imposes additional restrictions as described in the C# language reference
document.
The
compound assignment operators cannot be directly overloaded in C#. However
expressions of the form X op= Y are processed as X = X op Y, except that X is
evaluated only once. For example, suppose C() is a method returning an in
array, B() is a method returning an int, and C is also a method returning an
int. In this case the expression:
A()[B()] += C();
causes the methods A, B, and C to be evaluated only once.
The left hand operand of a compound assignment can be a property or indexer (covered
later) as long as property or indexer has a get and a set accessor.
The simple rule to remember is that the following is permitted:
X op= Y
only if both of the following are permitted:
X op Y
X = Y
The
increment (++) and decrement (- -) operators can be overloaded for user-defined
structs/classes in C#.
The increment and decrement operators are unique
in that they can be used as prefix operators (++m) or as postfix operators
(m++). In C++ you can provide one operator for the prefix version (which takes
a single arg) and another operator for the postfix version (which takes a
dummy second arg of type int; noone claims this to be elegant but it does
work).
In C# a single operator is used for both the prefix and postfix
versions. The compiler arranges for the result of a prefix call to be the
value of the operand before the call takes place, and the value of a postfix
call to be the result of the call.
This compiler magic works when the
type being overloaded with ++ or - - is a struct type. However, if the type is
a class (a reference type) then the value of the perfix call and postfix call
will be the same. This is a minor nit though because operator overloading is a
technique that naturally fits with value types and doesn't really make sense
for reference types.
The parameter type and return type of an increment
and decrement operator must be the containing type.
Some
operators naturally come in pairs. For example if you can compare two values
to see if they are equal using the == operator is is reasonable to assume you
can also compare them using the != operator. C# enforces this. If you declare one
you must declare the other or the program will not compile. The == != are also
important as a pair because nullable types (eg database booleans that can the value
true false or null) it is not the case the a == b implies !(a != b).
All
types referenced in an operator declaration must be at least as accessible as the
operator itself. Although an operator must be declared public, it may be inside
a struct/class with restricted accessibility (covered fully later).
A few
operators imposes additional restrictions as described in the C# language reference
document.
The
operator true and operator false pair are designed for tri-state nullable
types such as database booleans that can hold the value true, false, or null.
Implementing operator true and operator & for a struct or class has
the effect of creating an operator && for the type in question. This
operator will retain short circuit semantics but will not return a built-in
bool but the type in question.
Implementing operator false and operator |
for a struct or class has the effect of creating an operator || for the type
in question. This operator will retain short circuit semantics but will not
return a built-in bool but the type in question.
Interested readers should
refer to a complete example in the struct chapter of the language reference.
A
conversion operator converts from a source type to a target type. The source type
is indicated by the type of the conversion operator. The target type is indicated
by the return type of the conversion operator. A class or struct is permitted
to declare a conversion from source S to target T provided all of the following
are true:
S and T are different types
Either S or T is the containing struct or
class
Neither S nor T is object
Neither S nor T is an interface type
T is not a base class of S (only possible if
T and S are not structs)
S is not a base class of T (only possible if
T and S are not structs)
the declaration would create an implicit and
an explicit conversion from S to T
From the second rule it follows that you can declare a user-defined conversion
from a struct or class to a simple type (such as int) and vice versa but never
from a simple type to another simple type.
A user-defined conversion is
the only form of function for which the return type is part of the
signature.
In C# a single argument constructor is never a converting
constructor (as it is in C++ if the constructor is not explicit).
A
conversion operator converts from a source type to a target type. The source type
is indicated by the type of the conversion operator. The target type is indicated
by the return type of the conversion operator. A class or struct is permitted
to declare a conversion from source S to target T provided all of the following
are true:
S and T are different types
Either S or T is the containing struct or
class
Neither S nor T is object
Neither S nor T is an interface type
T is not a base class of S (only possible if
T and S are not structs)
S is not a base class of T (only possible if
T and S are not structs)
the declaration would create an implicit and
an explicit conversion from S to T
From the second rule it follows that you can declare a user-defined conversion
from a struct or class to a simple type (such as int) and vice versa but never
from a simple type to another simple type.
A user-defined conversion is
the only form of function for which the return type is part of the
signature.
In C# a single argument constructor is never a converting
constructor (as it is in C++ if the constructor is not explicit).
common
bounds checking must be duplicated inside the get and the set accessor.
You can only use a property on a definitely assigned variable. For
example:
Pair p;
p.X = 0; // compile time error
This is because the set accessor of the X field can contain any statements the
programmer wishes. It does not even have to set a field since the property
could be the implementation of a derived (deduced) attribute.
Pair p = new Pair();
p.X = 0; // OK