Programming - C#
These are my notes from learning C#, collected into a single page.
Compiling from the command-line
First, make sure the path is set up correctly. In a command-prompt window, run the following:
C:\Program Files (x86)\Microsoft Visual Studio 12.0\Common7\Tools\vsvars32.bat
01 - Basic command-line application
// hello.cs // arguments: A B C D using System; namespace RCS { public class Program { // Mandatory static Main method, the entry-point to the application public static int Main(string[] args) { Console.WriteLine("Hello, World!"); Console.WriteLine("Found {0} command line arguments:", args.Length ); for (int i=0; i < args.Length; i++) { Console.WriteLine("{0}", args[i]); } return 0; } } }
02 - Namespaces and Assembly References
Nested namespaces can be collapsed using the . syntax:
namespace Outer.Inner { }
is the same as:
namespace Outer { namespace Inner { } }
Using
Specifies namespaces required by a unit:
using System.Console
It allows for aliases:
using Output = System.Console; class Hello { public static void Main() { Output.WriteLine("Hello"); } }
External Aliases
To avoid a name conflict in an external assembly, an alias can be supplied to the compiler, either via a /reference:Alias = full.assembly.name.dll on the command-line, or using the Alias property in the Visual Studio reference properties.
Assemblies
By default the C# compiler only automatically imports mscorlib.dll -- any other assemblies must be explicitly specified.
For the command-line compiler use the /r: switch:
csc ProgramUsingSystemNet /r:System.Net.dll
03 - Basic Data Types (Common Type System)
Type | Size | CTS Type | Description |
---|---|---|---|
byte | 1 | System.Byte | Unsigned byte |
sbyte | 1 | System.SByte | Signed byte |
short | 2 | System.Int16 | Signed short |
ushort | 2 | System.UInt16 | Unsigned short |
int | 4 | System.Int32 | Signed integer |
uint | 4 | System.UInt32 | Unsigned int |
long | 8 | System.Int64 | Signed big integer |
ulong | 8 | System.UInt64 | Unsigned big integer |
float | 4 | System.Single | Floating point number |
double | 8 | System.Double | Double-precision floating point number |
decimal | 8 | System.Decimal | Fixed-precision number |
char | 2 | System.Char | Unicode character |
bool | 1 | System.Boolean | Boolean value |
string | - | System.String | Unicode string |
object | - | System.Object | Root type |
Note that string and object are reference types. All the others are value types.
Also note that strings do not act like normal reference types, because strings are immutable -- if you change a string, a completely new string is created. This means that after the following lines, s1 and s2 hold different values:
string s1 = "First string"; string s2 = s1; s1 = "Modified string";
At this point, s2 still holds "First string".
04 - ref and out Parameters
These are akin to Delphi 'var' parameters. The main difference between them is that ref does not accept uninitialised parameters, whereas out does. There is an implied difference in the way that they are expected to be used: ref parameters are for parameters which are passed in and modified in some way, whereas out is for parameters which are simply used to return values from the method.
Note: In CLR they are identical.
Slightly bizarrely the out (or ref) keywords have to be supplied when calling the method as well as in the declaration:
int x; int y; Point p = new Point; p.GetPoint(out x, out y);
Value Types and Reference Types
Value types are allocated on the stack, and assigning a value from one variable type to another creates a copy of the value.
Reference types are allocated on the heap (and must be created using 'new'), and copying from one reference variable to another creates two references to the same value.
05 - Overloading
Overloading of methods is done simply by declaring identically-named methods with differing signatures (there is no 'overload' keyword, or any other way of indicating that the methods are overloaded).
// Overloaded Add() method. static int Add(int x, int y) { return x + y; } static double Add(double x, double y) { return x + y; } static long Add(long x, long y) { return x + y; }
06 - Exceptions
Easiest to demonstrate by example. Note that you can have multiple catch blocks.
using System; class Test { static int Zero = 0; static void AFunction() { try { int j = 22 / Zero; } catch (DivideByZeroException e) { Console.WriteLine("DivideByZero {0}", e); } catch (Exception e) { throw(new Exception("An unanticipated exception occurred in AFunction()", e)); // You could instead do other handling here and then simply re-throw: // // Console.WriteLine("Something bad happened!"); // throw; } } public static void Main() { try { AFunction(); } catch (Exception e) { Console.WriteLine("Unexpected exception {0}", e); } } }
.NET has a top-level handler system for exceptions, and allows application-specific handlers to be added to it:
static void Main(string[] args) { AppDomain.CurrentDomain.UnhandledException += UnhandledExceptionHandler; // ... other code ... } static void UnhandledExceptionHandler (object sender, UnhandledExceptionEventArgs e) { Exception exception = (Exception) e.ExceptionObject; Console.WriteLine(exception); System.Diagnostics.Debugger.Break(); }
When you throw a new exception from within a catch handler, this wraps the original exception in a new exception. The chain of exceptions can be accessed using the innerException property of the Exception class, which contains the Exception that was originally caught.
Generally a new Exception should be thrown if it can be used to add details about the actual exception that occurred.
When re-throwing the same Exception, use 'throw', not 'throw e', i.e. do not explicitly throw the caught Exception, as this will actually create a new Exception stack and discard the original one, losing the information.
Exception Members
- Exception.TargetSite.DeclaringType (class)
- Exception.TargetSite.MemberType
- Exception.Message
- Exception.Source (assembly)
- Exception.StackTrace
- Exception.HelpLink (blank by default)
- Exception.Data (dictionary for additional information)
Example
ex = new Exception(string.Format("Error in {0}", "
You can check if an error is actually from the .NET runtime:
catch (Exception e) { if e is SystemException Console.WriteLine(".NET Runtime error: {0}", e.Message); }
07 - Custom Exceptions
Custom exceptions should inherit from Exception. Exceptions are perfectly ordinary classes.
From Microsoft MSDN: "For most applications, derive custom exceptions from the Exception class. It was originally thought that custom exceptions should derive from the ApplicationException class; however in practice this has not been found to add significant value."
Example
This conforms to Microsoft's "best practices" for Exception classes. Visual Studio includes a code snippet ('exception') that can help defining these.
[Serializable] public class ExampleException : Exception { public DateTime ErrorTimeStamp { get; set; } public string CauseOfError { get; set; } // Best practice: define a default constructor public ExampleException() { } // Best practice: define a constructor that takes the message param public ExampleException(string message): base(message) { } // Best practice: define a constructor to handle "inner exceptions" public ExampleException(string message, SystemException inner) : base(message, inner) { } // Best practice: protected constructor for inner exceptions protected ExampleException( System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) : base(info, context) { } // Pass message to parent constructor. public ExampleException(string message, string cause, DateTime time) :base(message) { CauseOfError = cause; ErrorTimeStamp = time; } }
08 - Inner Exceptions
If an exception happens within a 'catch' block it should be caught, and a new exception raised as a inner exception (hence the inner exception constructor in the above code):
catch(Exception e) { try { // Try to handle the exception... } catch (Exception inner) { // Something went wrong, so pass the current error message and // the inner exception to another exception throw new ExampleException(e.Message, inner); } }
09 - Access: internal
Classes can be public, protected, or internal. Classes marked as internal are only accessible to other classes in the same assembly.
The same specifier can be used on member variables, with the same result that they are then accessible to other classes in the same assembly.
It also possible to have a protected internal field, which is accessible to descendant classes and to other classes in the same assembly.
Types (e.g. classes) are implicitly internal. Member fields are implicitly private.
Classes can only be public or internal.
10 - Variable-Length Parameter Lists
Like so:
public void Write(string label, params object[] args) { WriteString(label); foreach (object o in args) { WriteString(o.ToString()); } } Write("Label:", "One", 2, "Three");
It's worth noting that a parameter type of object will accept anything passed as it, as it is the lowest-level type.
11 - Optional Parameters
C# methods or functions can take optional parameters, declared in the expected way:
static void EnterLogData(string message, string owner = "Programmer")
12 - Named Arguments
The calling code can identify a parameter by name instead of position, which can sometimes help for clarity.
Assume the following method exists:
public static void LogMessage(string message, bool includeDateAndTime)
It would normally be called like this:
log.LogMessage("Message", true);
However, the argument name can be used, which makes the meaning of 'true' clearer:
log.LogMessage("Message", includeDateAndTime: true);
Also, using optional parameters with named parameters allows a function to be called with only those parameters that want something other than the default.
Note that once you have used a named parameter in a function call, any following parameters must also be named.
13 - Nesting
Classes, interfaces, structs, delegates, and enums can all be nested inside a class.
14 - Inherited Classes
Simply declared:
class Original { } class Descendant: Original { }
No multiple inheritance -- classes must descend from a single ancestor.
The sealed keyword prevent inheritance:
sealed class NoInheritanceAllowed { }
Note that C# structures are implicitly sealed.
15 - Initialising a Base Constructor
Assuming that the ancestor class has a constructor which takes the listed set of parameters:
public Manager(string fullName, int age, int empID, float currPay, string ssn, int numbOfOpts) : base(fullName, age, empID, currPay, ssn) { // This property is defined by the Manager class. StockOptions = numbOfOpts; }
16 - Composition
Instances of other classes can be created at the declaration level:
public partial class Employee { // Contain a BenefitPackage object. protected BenefitPackage empBenefits = new BenefitPackage(); // Expose certain benefit behaviors of object. public double GetBenefitCost() { return empBenefits.ComputePayDeduction(); } // Expose object through a custom property. public BenefitPackage Benefits { get { return empBenefits; } set { empBenefits = value; } } }
17 - Nested Classes
As well as structs, enums, constants, member fields, etc., a class can contain other classes.
public class OuterClass { // A public nested type can be used by anybody. public class PublicInnerClass {} // A private nested type can only be used by members // of the containing class. private class PrivateInnerClass {} }
* Nested types allow control over the access level of the inner type.
- Nested types can access private members of the containing class.
- Nested types are often only useful as helpers for the containing class
18 - Abstract Classes
These cannot be instantiated, only inherited from.
abstract class Base { }
Abstract methods must be implemented by descendant classes (and note that the 'abstract' keyword replaces the 'virtual' keyword, which is not used in this case).
public abstract void MustImplement();
Only abstract classes can have abstract methods.
19 - Virtual Methods
As with Delphi, virtual methods must be marked as such, and overriding methods must likewise be indicated:
class Ancestor { public virtual void GiveBonus(float amount) { ... } } class Descendant: Ancestor { public override void GiveBonus(float amount) { // The base method can be called like this base.GiveBonus(amount); // ... other processing here ... }; } class FinalDescendant: Descendant { // Declaring the method as sealed prevents further overriding public override sealed void GiveBonus(float amount) { ... } }
If a method is not defined as virtual it is still possible to override it in a descendant class by using the 'new' keyword. This makes the method a complete replacement for the ancestor method, which is then completely hidden from the descendant class.
public new void Draw() { ... }
20 - Casting
It is always possible to store a derived class instance in a base class reference:
class Base { public void MethodInBase() { ... } } class Derived: Base { public void MethodInDerived() { ... } } // Implicit case of Derived as Base Base base = new Derived() // The Base methods can be called directly base.MethodInBase(); // To access the Derived methods a cast is required (Derived)base.MethodInDerived();
Casts are evaluated at runtime, and raise exceptions if they are invalid. The 'as' keyword can be used to check a cast:
Derived derived = base as Derived; if (derived == null) Console.WriteLine("Not a Derived instance!"); else derived.MethodInDerived();
The 'is' keyword can also be used:
if (base is Derived) (Derived)base.MethodInDerived(); else Console.WriteLine("Not a Derived instance!");
21 - Constructors
If no constructor is specified, there is a default constructor which has no parameters.
Constructors can be overloaded. As soon as a constructor with a non-empty signature is created, the default constructor is no longer available, and one must be explicitly supplied if it will be required.
One constructor can call another in the same class, using 'this':
public MyObject() { this.x = 0; this.y = 0; } public MyObject(int x) { this.x = x; } public MyObject(int x, int y): this(x) { this.y = y; }
Constructors can call inherited constructors in a similar way, but using 'base' instead of 'this':
class Original { int m_x; public Original(int x) { m_x = x; } } class Descendant: Original { int m_y; public Descendant(int x, int y): base(x) { m_y = y; } }
22 - Object Initialisation
Properties can be initialised when calling a constructor:
MyObject obj = new MyObject() { x = 10, y = 20 };
The parentheses can be omitted:
MyObject obj = new MyObject { x = 10, y = 20 };
23 - Private Constructors
It's possible to specify a class constructor as being private, in which case it cannot be called from outside the class. In this scenario, it is not possible to instantiate the class, but static functions in it can still be accessed.
This is used for classes which are nothing more than a collection of static functions, and for creating singletons (the singleton class exposes a static function which returns the single, internally-maintained instance of the class).
24 - Initialisation of Member Fields
When declaring member fields for classes these can be initialised at the same time:
class Point { int m_x = 0; int m_y = 0; // ... }
25 - Destructors
C# doesn't have any. It has finalizers, which are called when the object is destroyed by the garbage collector and can be used if the class holds resources that need to be cleaned up.
26 - Static Members
Static fields, constructors, and methods are associated with the class rather than with the instance. They can be called/accessed without an instance of the class being available, using the class itself instead:
class StaticTest { public static int instanceCount; public StaticTest() { instanceCount++; } } class Test { public static void Main() { st1 = new StaticTest(); print StaticTest.instanceCount; st2 = new StaticTest(); print StaticTest.instanceCount; } }
Note that unlike some other languages C# considers it an error to attempt to access a static field (or other static object) through an instance rather than through the class.
Static constructors are possible and will be called at some indeterminate point before the first instance of the class is created.
27 - Static Classes
Static classes are essentially nothing more than containers for related static methods. Specifying a class as static prevents instance methods from being added to it.
28 - Static Constructors
If defined, these are called before the first time the class is instantiated, and are never called again for the lifetime of the running application. They are used to safely set static member field values if these need to be set at runtime rather than compile-time.
A static constructor cannot take any parameters.
29 - Constants
Constants are declared with the const keyword. For obvious reasons they must be initialised at the same time, and can only be initialised with literals. Constants are also static.
class Test { public const double APPROX_PI = 3.141; }
30 - Read-Only Fields
The restriction on constants that they can only be initialised with literals can be avoided by using a read-only field instead. These fields are initialised in a static constructor, and cannot subsequently be changed.
class Test { public readonly int APPROX_PI; public static void Test() { APPROX_PI = 22 / 7; } }
Read-only fields can be static, in which case they must be assigned in a static constructor.
31 - Partial Classes
These allow classes to be defined across more than one file. A common use is with generated code, where the generated code is in one file, and the code supplied by the programmer is in a separate file.
All the parts of the class must include the 'partial' keyword.
partial class Test { public void generated_method() { // ... } } partial class Test { public void handcrafted_method() { // ... } }
In the above example the classes would usually be in different files.
It also possible to have partial methods. Essentially the generated code includes a declaration but no body. The other part of the class can include the method with a body. If it does not do so, it is assumed that the method is not required, and the compiler will remove it.
Partial methods cannot return values (they must be void and have no out parameters).
32 - Properties
Properties encapsulate getters and setters in a simple format:
private string empName; public string Name { get { return empName; } set { if (value.Length > 15) Console.WriteLine("Error! Name must be less than 16 characters!"); else empName = value; } }
Properties can optionally only define a get or a set (for read-only or write-only versions). They can also be static.
For very simple implementations a short-hand version can be used (these are called automatic properties):
public int Speed { get; set; }
Automatic properties must include both get and set. The values behind them will be set to the default values for the type (note that reference values will default to null).
Note that the value behind an automatic property is not readily accessible without code trickery. If either the get or set needs to be changed from the simple version defined by the automatic property you should implement a full getter and setter, using your own private field for the property.
33 - Interfaces
Interfaces specify the methods that classes must implement, and are rather like abstract classes except that you can inherit from multiple interfaces, and they do not contain any implementation code themselves.
Like Object Pascal the 'is' and 'as' operators are supported, and they work in the same way. 'as' will return null if the interface is not supported.
Methods which take an interface parameter can accept any class instance that supports the specified interface:
public void SomeMethod(ISomeInterface interfacedObject) { ... }
Note that passing an object which does not support the interface will be caught at compile-time.
Interfaces can be return values from method functions.
Arrays of interfaces are perfectly viable (and can contain any objects which support the required interface).
IExample[] ExampleObject = { new ExampleOne(), new OtherObject() };
Interfaces support 'as' and 'is'.
Incorrectly type-casting an object to an unsupported interface raises an InvalidCastException.
34 - Explicit Interface Implementation
If a class uses multiple interfaces, and these interfaces include duplicated methods (which is perfectly acceptable), it is possible to implement separate methods for each one if required:
class Octagon : IDrawToForm, IDrawToMemory, IDrawToPrinter { // Explicitly bind Draw() implementations // to a given interface. void IDrawToForm.Draw() { Console.WriteLine("Drawing to form..."); } void IDrawToMemory.Draw() { Console.WriteLine("Drawing to memory..."); } void IDrawToPrinter.Draw() { Console.WriteLine("Drawing to a printer..."); } }
However, any explicit interface implementation is automatically private, and the method cannot be called in its simple form. Instead, any call must typecast it to the required interface first:
oct = new Octagon(); ((IDrawToForm)oct).Draw(); // Or: (oct as IDrawToForm).Draw();
It is still acceptable to implement a single method (a Draw() method for the above example) if the required behaviour for each interface method is the same.
Interfaces can extend other interfaces, in the same manner (and using the same declaration syntax) that classes can inherit from other classes:
public interface DescendantInterface: BaseInterface { }
Interfaces can extend multiple interfaces.
As with classes, explicit interface implementation can be used if different implementations of duplicate methods are required.
35 - switch Statement
Every block must have a break or a goto (another case). C# allows string identifiers for the cases (yes!).
switch(identifier) { case "A": case "B: DoStuff(); break; case "C": DoOtherStuff(); goto case "X"; case "M": SomethingElse(); break; case "X": More(); break; default: LastChance(); break; }
36 - goto Statement
As in the switch statement above, goto can be used to jump to a label. In cases other than the switch statement, the label is a name followed by a colon.
if something_is_wrong { goto exitline; } else { do_normal_processing(); } exitline: return -1;
37 - while Statement
Completely standard:
while (expr = true) { DoSomething(); }
It supports break and continue.
There is also a do while:
do { Something(); } while (expr = true);
38 - for Statement
Completely standard:
for (i = 0; i < 10; i++) { DoSomething(i); }
39 - foreach Statement
Used to iterate over any enumerable collection, using the standard syntax:
foreach(string Name in someMap.Keys) { Console.Write(Name); }
Note that you cannot change the value of the enumerated item (in this case, you cannot assign a new value to Name).
40 - Enums
enum NumType { Zero, One, Two, Three, Hundred = 100, Thousand = 1000 }
The underlying type can be specified (it defaults to System.Int32):
enum NumType: byte { Zero, One, Two, Max = 255 }
The full type name must be specified when referencing an enum:
NumType valueTwo = NumType.Two;
41 - Arrays
Simple declaration:
int[] = new int[5];
Arrays can be initialised when they are declared:
string[] strArray = new string[3] { "One", "Two", "Three" };
The number of elements does not need to be declared:
string[] strArray = new string[] { "One", "Two", "Three" };
In fact, the entire 'new' segment can be omitted:
string[] strArray = { "One", "Two", "Three" };
42 - Multidimensional Arrays
These can be defined as rectangular arrays or as jagged arrays. In rectangular arrays the number of elements in each subarray is always the same:
int[,] rectArray; rectArray = new int[6, 10];
Jagged arrays are essentially arrays of subarrays, where each subarray is created separately and can be of a different length.
int[][] jaggedArray = new int[5][]; for (int i = 0; i < jaggedArray.Length; i++) { jaggedArray[i] = new int[i + 5]; }
Arrays can be method parameters and can be return values. In both cases the size of the array does not need to be declared.
43 - Scope
C# does not allow variables to be redeclared in an inner scope. The following won't compile:
void Test() { int x = 10; for (i = 0; i < 10; i++) { int x = 5; Console.Write("Test: {0}", x + i); } }
44 - Initialisation
Structs and classes have their fields initialised to null values. Other data types will raise a compiler error if they remain uninitialised.
45 - Nullable Values
C# supports the concept of value types which can hold a null value as well as the usual values. Reference types can always have a null value.
Nullable types are indicated by a ? suffix:
bool?[] nullableBools = { true, false, null }
46 - Null Coalescing Operator
Allows a default value to be supplied when assigning a variable:
string test = name ?? "";
47 - checked and unchecked
These allow for overflow exceptions to be raised or ignored. The default (curiously) is unchecked -- i.e. overflow exceptions are ignored and do not raise an error.
They can also be applied to single statements.
uint value1 = 312; byte value2; value2 = unchecked((byte) value1);
48 - Enumerable Interfaces
The foreach operator can be applied to any class which supports the IEnumerable interface. This interface has a single method, GetEnumerator, which must return an System.Collections.IEnumerator interface.
The standard array type supports the IEnumerable interface. Any class which wants to expose the elements of the array for external use can simply use the array's GetEnumerator() method:
public IEnumerator GetEnumerator() { // Return the array object's IEnumerator. return innerArray.GetEnumerator(); }
The method can be obscured using explicit interface implementation to avoid its use by anything other than foreach (which will look for this specific implementation):
IEnumerator IEnumerable.GetEnumerator() { // Return the array object's IEnumerator. return innerArray.GetEnumerator(); }
49 - Iterator Methods
An alternative way to support the IEnumerable interface is to implement a GetEnumerator() method which acts an as iterator.
public IEnumerator GetEnumerator() { foreach (System.Object item in innerArray) { yield return item; } }
The following is also valid, though weird, and illustrates the point that each call to the method gets the results of the next 'yield' statement:
public IEnumerator GetEnumerator() { yield return innerArray[0]; yield return innerArray[1]; yield return innerArray[2]; yield return innerArray[3]; }
Other methods can also use the 'yield' keyword and will act in the same way, except that they must return an IEnumerable interface (not IEnumerator). As with GetEnumerator() they will return the result of the next 'yield' statment with each call.
public IEnumerable GetItems(bool reversed) { if (reversed) { for (int i = innerArray.Length; i != 0; i--) { yield return innerArray[i-1]; } } else { foreach(System.Object item in innerArray) { yield return item; } } }
50 - ICloneable Interface
The ICloneable interface has a single method, Clone() which returns an object that is a copy of the object it is invoked on. Because it returns a basic object, the result must be typecast to the required type.
public class Example: ICloneable { // ... public object Clone() { return new Example(); } }
There is a MemberwiseClone() method which can be used to simplify this:
public object Clone() { return MemberwiseClone(); }
This only performs a shallow copy. To correctly copy any member fields which are class instances, the method must be extended:
public object Clone() { // Use MemberwiseClone() to get the basic copy Example clone = (Example)this.MemberwiseClone(); // Now create any contained objects InteriorClass inner = new InteriorClass(); // Copy any details from the current instance inner.field = this.inner.field; // Assign the contained object to the clone clone.inner = inner; // Return the final copy return clone; }
51 - IComparable and IComparer
The IComparable interface has a single method, CompareTo(), which takes an object as a parameter. It should return 1 if the current object should be regarded as 'greater' than the supplied object, -1 if the current object should be regarded as 'less' than the supplied object, and 0 if they are at the same level.
public class Example: IComparable { int id; // ... int IComparable.CompareTo(object o) { Example temp = o as Example; if (temp != null) { if (this.id > temp.id) return 1; if (this.id < temp.id) return -1; else return 0; } else throw new ArgumentException("Supplied object is not an Example"); } }
Note that the primitive types (such as int) support the IComparable interface, so the comparison above could be simplified:
if (temp != null) return this.id.CompareTo(temp.id); else throw new ArgumentException("Supplied object is not an Example");
52 - IComparer
Helper classes implementing the IComparer interface can be used to allow sorting by different parameters, creating a new class for each sort type required.
These implement a Compare(object, object) method which compares the two supplied objects and returns the same values as returned by CompareTo.
public class ExampleNameComparer: IComparer { public static int Compare(object o1, object o2) { Example t1 = o1 as Example; Example t2 = o2 as Example; if ((t1 != null) && (t2 != null)) { return String.Compare(t1.Name, t2.Name); } else throw new ArgumentException("Invalid object type supplied"); } }
The array type has a version of Sort which takes an object implementing the IComparer interface:
Array.Sort(someArray, new ExampleNameComparer());
As a better alternative, the main class can implement a static method which returns the appropriate comparer class:
public class Example: IComparable { public static IComparer SortByName { get { return (IComparer)new ExampleNameComparer(); } } } Array.Sort(arrayOfExamples, Example.SortByName);
53 - Collections
.NET includes a set of non-generic collections, but these have generally been superseded by generic versions. The non-generic collections invariably store objects and have to be type-cast back to the required type when they are read. For value-types the values are actually boxed and unboxed -- boxing is done automatically when the values are added to the collection, and unboxing occurs when the values are type-cast (on reading them from the collection).
The resulting performance issues and lack of type-safety are addressed by the generic versions of these collections.
Generics allow the type to be specified when the collection is created, enforcing type-safety, and avoiding the necessity to box and unbox the values:
ListintList = new List (); intList.Add(10); List exampleList = new List (); exampleList.Add(new Example());
There are also generic versions of some class member methods:
int[] intArray = { 1, 5, 6, 2, 4, 3 } Array.Sort(intArray); foreach(int i in intArray) { Console.WriteLine(i); }
Generics can simplify classes which use interfaces like IComparable. For example, compare this with the example given under IComparable and IComparer above:
public class Example: IComparable{ int id; // ... // The actual class type can be specified int IComparable .CompareTo(Example o) { // No need to check that we have the right class type as it // is guaranteed by the compiler if (this.id > temp.id) return 1; if (this.id < temp.id) return -1; else return 0; } }
54 - Collection Initialisation
This uses a similar syntax to object initialisation:
ListintList = new List { 3, 5, 1, 8, 5 };
A more involved example, which combines collection initialisation and object initialisation:
ListmyListOfPoints = new List { new Point { X = 2, Y = 2 }, new Point { X = 3, Y = 3 }, new Point(PointColor.BloodRed){ X = 4, Y = 4 } };
Some generic collection classes:
- Dictionary
- LinkedList
- List
- Queue
- SortedDictionary
- SortedSet
- Stack
55 - default
As well as being used in switch statements, the default keyword can be used to set a value to the correct default for the type:
t = default(T);
This can be useful in generics if values need to be reset to defaults.
56 - Generic Type Constraints
When declaring a generic type, a 'where' clause can be added to restrict the allowed interfaces that the type can support. For example, the following generic only supports type which are classes, support the IDrawable interface, and have a default (parameter-less) constructor:
public class MyGenericClasswhere T : class, IDrawable, new() { ... }
Constraints:
- where T : struct
The type parameter
- where T : class
The type parameter
where T : new()
The type parameter
where T : NameOfBaseClass
The type parameter
where T : NameOfInterface
The type parameter
You can separate multiple interfaces as a comma-delimited list.
If a generic has multiple parameters (such as the Dictionary collection), there can be 'where' clauses for each parameter:
public class MyGenericClasswhere K : SomeBaseClass, new() where T : struct, IComparable { ... }
57 - Custom Generic Methods
As well as using the existing collections and methods you can create your own generic methods:
static void Swap(ref T a, ref T b) { T temp; temp = a; a = b; b = temp; }
Note that it is possible to omit the generic reference when calling a generic method which takes parameters, as the compiler can infer the correct method based on the types that you pass:
bool b1 = True; bool b2 = False; // This is allowed: Swap(ref b1, ref b2); // ...instead of this: Swap(ref b1, ref b2);
However, this is not good practice as it tends to hide the type from anyone looking at the method call.
58 - Delegates
A delegate object can hold one or more references to methods of a specific signature. Invoking the delegate (which is done exactly as if the delegate object were a method itself) results in the contained methods being called.
When a delegate object is created, it is created using the signature that the methods will have. For example:
public delegate int BinaryOperation(int x, int y);
This defines a delegate for methods which take two integer parameters and return an integer.
When a delegate object is created, an initial method can be passed to it:
public int Add(int x, int y) { return x + y; } BinaryOperation opHandler = new BinaryOperation(this.Add);
More commonly, though, methods are assigned to the handler using the += operator:
BinaryOperation opHandlerList; opHandlerList += this.Add;
Delegates are commonly used for event-handling. When an event occurs, the details of the event are passed to all the delegates that have registered as being interested. A common pattern is to provide a Register method which accepts a delegate object of the required type, and stores it in the list:
RegisterBinaryOperation(BinaryOperation m) { opHandlerList += m; } RegisterBinaryOperation(new BinaryOperation(this.Add));
It is also possible to use 'method group conversion' which simply allows the method itself to be passed, instead of the delegate object:
RegisterBinaryOperation(this.Add);
Delegates can also use generics:
public delegate void Observer(T arg);
Delegates are essentially an implementation of the Observer pattern.
Note that unlike Delphi's event-handlers (which delegates are the equivalent of), delegates can handle multiple observers.
59 - Action and Func Delegates
There are predefined Action and Func generic delegates which can be used instead of creating your own. Action takes up to 16 parameters, but returns void, whereas Func takes up to 16 parameters and returns a value.
SimpleMath m = new SimpleMath(); FuncOnAdd = new Func (m.Add);
Note that for Func the last parameter is the return type.
60 - Anonymous Methods
When defining the methods that will be assigned to method lists, you can use anonymous methods instead:
observable.OnEvent += delegate(object sender, Args e) { Console.WriteLine("From anonymous method: {0}", e.Message); };
Note that anonymous methods cannot access ref or out parameters of the method in which they are defined, but they can access other parameters and local variables. In other respects their scope is as with normal methods.
61 - Lambdas
Lambdas are essentially simplified anonymous methods. The following example uses a lambda instead of an anonymous method, to provide the callback for the List.FindAll() method (this illustrates another use for delegates, as standard callback mechanisms):
// Make a list of integers. Listlist = new List (); list.AddRange(new int[] { 20, 1, 4, 8, 9, 44 }); // Now process each argument within a group of // code statements. List evenNumbers = list.FindAll((i) => { Console.WriteLine("value of i is currently: {0}", i); bool isEven = ((i % 2) == 0); return isEven; });
Here is an even simpler version:
// Make a list of integers. Listlist = new List (); list.AddRange(new int[] { 20, 1, 4, 8, 9, 44 }); // Now process each argument within a group of // code statements. List evenNumbers = list.FindAll((i) => ((i % 2) == 0));
Note that the following is also acceptable, but possibly not good practice:
ListevenNumbers = list.FindAll(i => (i % 2) == 0);
Note that type inference is used to determine the type of the 'i' parameter, but this could be explicitly indicated instead:
ListevenNumbers = list.FindAll((int i) => (i % 2) == 0);
Any method that takes a delegate object can take a lambda expression instead.
62 - Events
Instead of providing Register and Unregister methods (which have to be manually coded for each class they appear in), you can define the delegates as events, which provides these methods automatically.
public class SimpleArgs: EventArgs { public readonly string Message; public CountDownArgs(string message) { Message = message; } } public class Observable { public event Action
63 - Indexer Methods
These allow a class to be treated as if it were a collection or array, by adding properties which can return elements from an internal collection on the basis of an index.
public class PersonCollection : IEnumerable { // Internal array private ArrayList arPeople = new ArrayList(); // Custom indexer for this class. public Person this[int index] { get { return (Person)arPeople[index]; } set { arPeople.Insert(index, value); } } }
This allows the class to be accessed via an index:
people = new PersonCollection(); // ... add entries ... // Access the entries Console.WriteLine(people[0].Name);
Indexers can also be strings:
public class PersonCollection : IEnumerable { private DictionarylistPeople = new Dictionary (); // This indexer returns a person based on a string index. public Person this[string name] { get { return (Person)listPeople[name]; } set { listPeople[name] = value; } } }
The above example could actual be done using the Dictionary itself, but it illustrates the point.
It is possible to overload indexers, so you could have a class which allowed access via an integer index or a string -- you simply define multiple accessor methods for the index property.
Multi-dimensional indexes are also possible:
public class SomeContainer { private int[,] my2DintArray = new int[10, 10]; public int this[int row, int column] { /* get or set value from 2D array */ } }
Indexers can be included in interfaces. The implementing classes must provide the appropriate accessor methods as normal:
public interface IStringContainer { string this[int index] { get; set; } }
64 - Overloading Operators
Where classes have obvious but non-supported operations that match with the existing operators it is possible to overload the operators and provide the required functionality:
// Overloaded operator +. public static Point operator + (Point p1, Point p2) { return new Point(p1.X + p2.X, p1.Y + p2.Y); }
Note that += and -= are automatically provided if the relevant + and/or - operators are overloaded. They cannot be overloaded otherwise.
65 - Overloading == and !=
public class Point { ... public override bool Equals(object o) { return o.ToString() == this.ToString(); } public override int GetHashCode() { return this.ToString().GetHashCode(); } public static bool operator ==(Point p1, Point p2) { return p1.Equals(p2); } public static bool operator !=(Point p1, Point p2) { return !p1.Equals(p2); } }
66 - Custom Conversion Routines
You can create custom conversion routines to allow explicit and implicit type-casting between types which could not otherwise be converted.
The following is intended to be a method in a Square struct which will accept a Rectangle struct and convert it, allowing a typecast from Rectangle to Square (which would otherwise not be possible as structs cannot inherit).
public static explicit operator Square(Rectangle r) { Square s = new Square(); s.Length = r.Height; return s; }
If instead you provide a method for implicit conversion, no type-casting is required to convert from one type to the other.
The following is a method in a Rectangle struct which allows a Square to be implicitly cast to a Rectangle:
public static implicit operator Rectangle(Square s) { Rectangle r = new Rectangle(); r.Height = s.Length; // Assume the length of the new Rectangle with // (Length x 2). r.Width = s.Length * 2; return r; }
67 - Extension Methods
Extension methods allow new methods to be added to existing classes without inheritance. They can also be applied to value types, as in this example:
static class ExtensionMethods { // This method allows any integer to reverse its digits. // For example, 56 would return 65. public static int ReverseDigits(this int i) { // Translate int into a string, and then // get all the characters. char[] digits = i.ToString().ToCharArray(); // Now reverse items in the array. Array.Reverse(digits); // Put back into string. string newDigits = new string(digits); // Finally, return the modified string back as an int. return int.Parse(newDigits); } }
The first parameter of an extension method uses 'this' and specifies the type that the method can be applied to. Other parameters can follow, but these will be standard parameters.
The extension method can then be applied to an instance of the relevant type:
int anInt = 123456; Console.WriteLine("Reversed: {0}", anInt.ReverseDigits());
Usually extension methods will be in a namespace and an assembly so that they can be referenced by units which require them, and do not impact anything else.
Extensions can also be applied to interfaces:
public static void PrintData(this System.Collections.IEnumerable iterator) { foreach (var item in iterator) { Console.WriteLine(item); } }
Any IEnumerable or IEnumerable<> can then reference this method.
68 - Anonymous Types
Anonymous types allow minimal structures to be created 'on the fly', like so:
static void BuildAnonType( string make, string color ) { // Build anon type using incoming args plus on hard-coded value var car = new { Make = make, Color = color, Speed = 50 }; // Note you can now use this type to get the property data! Console.WriteLine("You have a {0} {1} going {2} MPH", car.Color, car.Make, car.Speed); // Anon types have custom implementations of each virtual // method of System.Object. For example: Console.WriteLine("ToString() == {0}", car.ToString()); }
All anonymous types descend from System.Object and hence inherit all its properties (such as the ToString() method as shown above).
One common use of anonymous types is to create instances that are immediately assigned as items of a collection.
Equality of Anonymous Objects
Given two anonymous objects in the same assembly, with the same properties and property values:
- Equals() will say they are equal
- == will say they are not equal (they are different instances)
- GetType() will return the same name for both (i.e. they are the same type).
Given two anonymous objects with the same properties but different values:
- Equals() will say they are not equal
- == will say they are not equal
- GetType() will say they are the same type
Given two anonymous objects with different properties, all three checks will say that they are different and of different types.
Anonymous types can contain other anonymous types.
Directories and Files
System.IO.Directory.Exists(path)