ACCU Spring Conference, Christ Church College, Oxford, Friday 30th 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 30th 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 30th 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:
The important parts of the .NET VES (virtual execution system) are:
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 2nd-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.
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:
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
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:
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 28th 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.
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