After you have an understanding of the structure of an object-oriented program, the rest is pretty much just filling in the blanks. Now you can quickly go through each of the basic elements of a C# program.
I have discussed a little bit about an abstract class. You can use the abstract modifier to declare an abstract method or property. An abstract class has the following properties:
An abstract class cannot be instantiated.
A non-abstract class that is derived from an abstract class must implement all of the abstract methods and properties.
An abstract class must provide implementations for all of the methods of an inherited interface.
An abstract class can declare its “implementation” of an interface method to be abstract, thus forwarding the responsibility of implementation to the class that derives from it.
Listing A.31 shows the implementation of a basic abstract class.
It should be noted that an abstract method or property is implicitly virtual, and abstract methods and properties can only be defined in an abstract class. The code in Listing A.31 obeys these rules.
Listing A.32 shows a short example of using abstract in conjunction with an interface.
Notice that the abstract class either implemented the interface method (as in method A) or forwarded the responsibility on to the deriving class by making the interface method abstract (as in methods B and C).
This operator returns a null if the class is not derived from the specific class or interface. If it has in its hierarchy the class or interface, then a reference to the class or interface is returned. An example of using the as operator is shown in Listing A.33.
class C { } class B : C { } class A : B { } class AsMain { public static void Main() { A a = new A(); B b = new B(); C c = new C(); try { B bcc = (B)c; } catch(Exception e) { Console.WriteLine(e); } B bc = c as B; if(bc == null) Console.WriteLine("C is not a B"); B ba = a as B; if(ba == null) Console.WriteLine("A is not a B"); } } |
The code of Listing A.33 outputs the following:
System.InvalidCastException: Specified cast is not valid. at Testing.AbstractMain.Main() C is not a B
At first, an exception is thrown because C is not derived from B and cannot be cast to a B object. By using as, a null is returned to avoid having the overhead of an exception.
This operator is used to access members of a base class from a derived class. It can only be called from within properties or methods in a derived class. It cannot be called from a static method. Typically, it would be called from a constructor:
public Foo(string s) : base(s)
This would call the base constructor for the object with an argument of a string. This operator can also be used from a method or property:
base.Bar();
This would call the Bar method in the base class. Typically, this would be used to disambiguate a call to a base class method that has the same signature as the calling method.
This statement terminates the closest enclosing loop or conditional statement. Look at Listing A.34.
public static void Main() { string input; Console.WriteLine("Starting loop."); while(true) { Console.Write("Input . . ."); input = Console.ReadLine(); if(input[0] == 'B') break; } } |
This program will continue to run until the user enters a string starting with 'B', at which point the program terminates.
Break is also used to exit a switch block.
A switch statement is an alternative to if when it is possible for an object to have more than two different values.
Listing A.50 shows the basic uses of a switch statement.
A case statement is always included as part of a switch statement. A switch statement is an alternative to if when it is possible for an object to have more than two different values. The case statement delimits the sections of code that should be executed if the match condition specified by the case statement occurs.
See Chapter 15 “Using Managed Exceptions to Effectively Handle Errors,” for examples of using try/catch/finally with exceptions.
This operator forces an exception to be thrown if an integer overflow occurs. The opposite unchecked makes sure that the output is not checked for overflow condition. Listing A.35 shows these two operators in action.
class CheckedMain { static short x = Int16.MaxValue; static short y = Int16.MaxValue; static int AddShort() { int ret = 0; try { ret = (short)(x + y); } catch(Exception e) { Console.WriteLine(e); } return ret; } static int AddCheckedShort() { int ret = 0; try { ret = checked((short)(x + y)); } catch(Exception e) { Console.WriteLine("Checked:"); Console.WriteLine(e); } return ret; } static int AddUncheckedShort() { int ret = 0; try { ret = unchecked((short)(x + y)); } catch(Exception e) { Console.WriteLine("Unchecked:"); Console.WriteLine(e); } return ret; } public static void Main() { Console.WriteLine("AddShort returns: {0} ", AddShort()); Console.WriteLine("AddCheckedShort returns: {0} ", AddCheckedShort()); Console.WriteLine("AddUncheckedShort returns: {0} ", AddUncheckedShort()); } } |
The output for Listing A.35 is as follows:
AddShort returns: -2 Checked: System.OverflowException: Arithmetic operation resulted in an overflow. at Testing.CheckedMain.AddCheckedShort() AddCheckedShort returns: 0 AddUncheckedShort returns: -2
The modifier const specifies that the field or local variable cannot be modified. As a field in a class, it might look like this:
public const int c1 = 5;
As a local variable, it might look like this:
const int c1 = 5;
If you attempt to modify a const value, the compiler generates an error like this:
error CS0131: The left-hand side of an assignment must be a variable, property or indexer
The continue directive forces execution to continue at the bottom of the nearest enclosing loop. Listing A.36 shows a possible usage of continue.
public static void Main() { string input; bool stop = false; Console.WriteLine("Starting loop."); while(!stop) { Console.Write("Input . . ."); input = Console.ReadLine(); if(input[0] != 'B') { continue; } stop = true; } } |
The line stop = true; is skipped until the user enters a string beginning with 'B'.
A default statement is always included as part of a switch statement. A switch statement is an alternative to if when it is possible for an object to have more than two different values. The default statement matches all cases that are not specifically specified by case statements.
A delegate wraps the signature of a function or method into a class. delegates have been likened to function pointers in C or C++. After a delegate is defined, it can be used to reference a method or function without having to know at compile time which function or method will be invoked. The key feature of a delegate is that it is a type-safe, and secure way to reference a function or method. delegates are covered in detail in Chapter 14, “Delegates and Events.”
The keyword begins a do/while block that continually executes until the expression in while evaluates to false. Note that unlike while, this statement always executes the enclosing block at least once. Listing A.37 shows an example.
public static void Main() { string input; bool stop = false; Console.WriteLine("Starting loop."); do { Console.Write("Input . . ."); input = Console.ReadLine(); if(input[0] == 'B') stop = true; } while(!stop); } |
If is a control statement that executes a statement block if the expression supplied as an argument to the if statement evaluates to true. The if statement has an optional else clause that is executed if the expression evaluates to false.
if(x > 0) Console.WriteLine("X > 0"); else Console.WriteLine("X <= 0");
An event wraps the functionality of a delegate into yet another class that only exposes two operators, the += operator for adding a delegate to an event list, and the -= operator for removing a delegate from an event list. Declaring an event requires two arguments. The first is the delegate signature for the method or function that will be added into this event, and the second is the name of the variable that holds the event.
Events are covered in detail in Chapter 14.
This modifier forces the user to do an explicit cast conversion. This is the opposite of implicit, which allows for a silent, automatic conversion. Listing A.38 shows a simple application using implicit and explicit.
struct Complex { double real; double imag; public Complex(double real, double imag) { this.real = real; this.imag = imag; } public override string ToString() { return (string.Format("({0} , {1} i)", real, imag)); } public static explicit operator Complex(double r) { return new Complex(r, 0); } public static implicit operator Complex(double [] n) { if(n.Length != 2) throw new ArgumentException(); return new Complex(n[0], n[1]); } } class ExplicitMain { static void Main(string[] args) { Complex cn = new Complex(1,0); Console.WriteLine("{0} ", cn); cn = (Complex)2; Console.WriteLine("{0} ", cn); double [] da = new double [] {3,0} ; cn = da; Console.WriteLine("{0} ", cn); } } |
Notice that the conversion from double to Complex requires an explicit cast, whereas the conversion from an array of double to Complex is done implicitly. If the explicit cast is removed, then the compiler flags it as an error, indicating that no conversion can be done implicitly.
Commonly used with the DllImport attribute, this modifier declares that a function is defined externally. There are many examples of using the extern directive in Chapter 7, “Leveraging Existing Code—P/Invoke.”
Finally is a way of stipulating that a section of code must be executed no matter how the execution path leaves the current scope.
See Chapter 15 for examples of using try/catch/finally with exceptions.
This statement prevents the garbage collector from relocating memory. It “pins” the memory for the duration of the associated block of statements. See Listing A.28 for an example.
This statement sets up a loop that first executes the statements in the initialization section. It then executes the iterator section and the statement associated with the for loop as long as the expression evaluates to true. Listing A.39 shows a simple example of using for.
for(int i = 0; i < 5; i++) Console.WriteLine("{0} ", i); |
This simple section of code simply prints the numbers 0 through 4.
This construct allows you to iterate through any collection that supports the GetEnumerator method that returns an enumerator. A simple example looks like Listing A.40.
string [] sa = new string [] { "This", "is", "a", "test" } ; foreach(string s in sa) { Console.WriteLine(s); } |
Arrays of any value support the foreach construct. In addition, the collections classes support the required enumerators to use in foreach. It is also possible to use foreach with user-defined classes. When building a user-defined class that can work with foreach, you can use two approaches. The first approach enables the user to iterate through a collection only in C#. The second approach not only allows the user to iterate through a collection with C# and foreach, but it also allows iteration using another language, such as VB. Listing A.41 shows the C#-specific approach.
To allow foreach to iterate through a custom collection, you need to follow these rules:
You need to define the collection as part of an interface, struct, or class.
The class or structure needs to define a method GetEnumerator that returns an Enumerator type.
The Enumerator type returned by GetEnumerator needs to implement a Current property that returns an item of the correct type or a type that can be converted to the underlying type contained in the collection.
The Enumerator type also must implement a MoveNext method that returns a bool of false when the end of the collection has been reached.
Listing A.42 shows a generic approach to iterating through collections.
For the generic approach, you need to follow all of the rules that apply to the C#-specific case. In addition, you need to implement the IEnumerable interface.
Transfers control to a labeled statement. Unlike its C++ predecessor, this jump has some restrictions on where it can go. For example, a C# goto cannot jump into a statement block.
See switch for how to use goto in a switch statement.
if is a control statement that executes a statement block if the expression supplied as an argument to the if statement evaluates to true. The if statement has an optional else clause that is executed if the expression evaluates to false.
if(x > 0) Console.WriteLine("X > 0"); else Console.WriteLine("X <= 0");
See explicit for an example of how to use explicit versus implicit.
An interface is a way of describing a set of methods that have no implementation. It is the responsibility of the class or struct inheriting from an interface to implement each of the methods described as part of the interface. The methods declared as members of an interface are implicitly abstract. It is an error to explicitly declare the methods abstract. You would use an interface if you wanted to guarantee that certain methods would be implemented on a certain set of your classes. You can even check to make sure that a given instance implements a particular interface either through a direct cast and catching an exception or by using the as operator. Listing A.43 (or the associated code in interface.cs) illustrates the usages of the interface keyword.
The internal access modifier allows access to a particular type only from files that comprise a single assembly. Other assemblies do not have access to this type. Listing A.44 shows a library created from external.cs. That library is being used with internal.cs to create a program.
(internal.cs) // csc /r:external.dll internal.cs using System; namespace Testing { class InternalMain { static void Main(string[] args) { InternalClass ic = new InternalClass(); } } } (external.cs) // csc /t:library external.cs using System; namespace Testing { public class InternalClass { public InternalClass() { Console.WriteLine("Constructing InternalClass"); } } } |
Modify Listing A.44 to use the internal modifier. Now the listing looks like Listing A.45.
(external.cs) // csc /t:library external.cs using System; namespace Testing { internal class InternalClass { public InternalClass() { Console.WriteLine("Constructing InternalClass"); } } } |
The compile statement from Listing A.44 of internal.cs fails with an error indicating that the class is not accessible.
Test for runtime type compatibility with a given object. See the previous section on interfaces for examples of using this operator.
See Chapter 11, “Threading,” for examples of using the lock statement for synchronization.
The namespace keyword is used to create a scope for all of the members contained within the namespace block. When an identifier is enclosed in a namespace, it is fully qualified.
New constructs a new instance of an object.
Three types of operators can be defined: unary, binary, and conversion. During the discussion of user-defined value types, a Complex value was defined. This value showed examples of unary and binary operators.
A conversion operator must have an additional modifier of either explicit or implicit depending on how the conversion is to take place. See explicit for examples of conversion operators.
Unary operators that can be overridden are as follows:
+ - ! ~ ++ -- true false
Binary operators that can be overridden are as follows:
+ - * / % & | ^ << >> == != > < >= <=
All operators are static methods.
Out is one of the method parameters that describes how arguments are passed to and from a method call. Out specifies that the called method will fill in the value for the parameter. Listing A.46 shows how to use out.
public static int TestOut(out int i, out int j) { i = 2; j = 3; return 1; } public static void Main() { int a; int b; int c = TestOut(out a, out b); Console.WriteLine("{0} {1} {2} ", a, b, c); } . . . 2 3 1 |
An override modifier specifies that the method overrides the method with the same signature inherited from the base class. You cannot override a non-virtual or static method. See explicit for an example of the syntax used with override.
Params is a method parameter that specifies a variable number of arguments. Listing A.47 shows a simple example.
Private is a member access modifier. Members that are declared private are only accessible from the class or struct in which they are defined.
Protected is a member access modifier. Members that are declared protected are accessible from the class in which they are defined as well as from the class that inherits the class in which they are defined.
Public is a member access modifier. Members that are declared public have no access restrictions.
A readonly variable can only be modified during the declaration or in the constructor of a class or struct. This differs from a const variable, which cannot be modified anywhere in the code. Listing A.48 shows how readonly can be used.
class Readonly { // const int c1 = 5; readonly int c1; public Readonly() { c1 = 6; } public int Value { get { return c1; } } } class ReadonlyMain { public static void Main() { Readonly ro = new Readonly(); Console.WriteLine("{0} ", ro.Value); } } |
If you uncomment the line where the field c1 is declared as const, a compile-time error is generated from the line where c1 is assigned in the constructor.
Ref is similar to out. However, when a parameter is marked with ref, data will be transferred to the method as well as from the method, as in out.
Listing A.49 illustrates some important differences between ref and out.
class RefMain { public static int TestRef(ref int i, ref int j) { Console.WriteLine("Entering TestRef {0} {1} ", i, j); i = 5; j = 6; return 4; } public static int TestOut(out int i, out int j) { // Console.WriteLine("Entering TestOut {0} {1} ", i, j); i = 2; j = 3; return 1; } public static void Main() { int a; int b; int c = TestOut(out a, out b); Console.WriteLine("{0} {1} {2} ", a, b, c); // int d; // int e; int d = -1; int e = -2; int f = TestRef(ref d, ref e); Console.WriteLine("{0} {1} {2} ", d, e, f); } } |
If you remove the comments from the Console.WriteLine in TestOut, you get a compile-time error because out parameters are unassigned on entering the method. If you remove the comments from the declarations of variables d and e and comment the declarations that contain an assignment, you also get a compile-time error because ref parameters must be initialized.
A return causes a return from the enclosing method or function. Optionally, a return type can be supplied as an argument to this statement.
If a class is sealed, it cannot be used as a base class to construct another class. Sealed classes offer some performance benefits, so sealing a class should be considered if it is known ahead of time that this class will not be involved in inheritance hierarchies.
This function is similar to _alloca in the C runtime library. It allocates a block of memory on the stack. This function must be called in an unsafe context because it returns a pointer type. The memory allocated is not subject to garbage collection, so it is not necessary to use fixed to pin the memory. Listing A.50 shows how this function is used.
class StackallocMain { static unsafe void FillBuffer(out int[] buffer) { buffer = new int [10]; int count = buffer.Length; int *p = stackalloc int[count]; int *t = p; for(int i = 0; i < count; i++) *t++ = i; t = p; for(int i = 0; i < count; i++) buffer[i] = *t++; } static void Main(string[] args) { int [] buffer; FillBuffer(out buffer); foreach(int i in buffer) Console.WriteLine("{0} ", i); } } |
This sample simply allocates some memory, initializes the contents to an increasing integer, and copies that memory to an output buffer. Of course, the main purpose of this sample is to show the usage of stackalloc.
This keyword specifies that a member belongs to the type rather than to an instance of the type.
A switch statement is an alternative to if when it is possible for an object to have more than two different values.
Listing A.51 shows the basic uses of a switch statement.
enum Animals { Cat, Worm, Mammal } class SwitchMain { static void AnimalOperations(Animals animal) { switch(animal) { case Animals.Cat: Console.WriteLine("This is a Cat"); goto case Animals.Mammal; case Animals.Worm: Console.WriteLine("Worm"); break; case Animals.Mammal: Console.WriteLine("This is a mammal."); goto default; default: Console.WriteLine("This is also a vertebrate."); break; } } static void Main(string[] args) { AnimalOperations(Animals.Cat); AnimalOperations(Animals.Worm); AnimalOperations(Animals.Mammal); } |
Goto statements are almost as bad in C# code as they were in C++ or C code, so it's best to avoid them. As you can see from the previous example, it is not trivial to figure out just what will be printed. Use goto only when necessary. It is used here just to be complete in the description of the syntax of a switch statement.
This is a reference to the enclosing object instance.
See Chapter 15 for examples of using throw with exceptions.
See Chapter 15 for examples of using try/catch/finally with exceptions.
This operator is used to obtain a System.Type object for a given type. Listing A.52 shows how to use this operator.
class TypeofMain { public static void Main() { Type t = typeof(System.String); Console.WriteLine("Assembly: {0} ", t.Assembly); Console.WriteLine("Assembly Name: {0} ", t.AssemblyQualifiedName); } } |
The unchecked operator makes sure that the output is not checked for overflow condition. Listing A.35 shows an example of using both checked and unchecked operators in action.
This keyword marks a method as potentially not type safe. In an unsafe method, type safety checks are not performed. An unsafe method does not initialize the local variable automatically. Marking a method as unsafe enables a programmer to isolate unsafe code from safe code. Listing A.29 and Figure A.3 provide some examples of using the unsafe operator.
using has two different uses. One is to specify a namespace alias. The other is to define a scope at the end of which an object will be destroyed.
Listing A.53 shows how you can define a different alias for a namespace with using.
using MySystem = System; namespace Testing { using MyCompanyAlias = MyCompany.OutputFunctions; namespace MyCompany { namespace OutputFunctions { class OutputToConsole { public static void Line(string s) { MySystem.Console.WriteLine("Hello!"); } } } } class UsingMain { public static void Main() { MyCompanyAlias.OutputToConsole.Line("Hello!"); } } } |
The common usage is to just have “using System;”, which defines a blank namespace to be an alias to System, essentially prohibiting you from specifying the System namespace. Here, an alias is specified for System to be MySystem. Similarly, an alias has been defined to save some typing for MyCompany.OutputFunctions.
The other usage of using is to define a scope for an object lifetime. Many samples in Chapter 10, “Memory/Resource Management,” show resource allocation. The sample in Listing A.54 merely shows the syntax.
A virtual modifier on a method indicates that this method is a candidate to be overridden by a class that inherits from the class that is defining the method. In Listing A.28, the abstract keyword implicitly defines a method as virtual. You can replace just the definition of the Shape class with Listing A.55.
class Shape { public virtual void Draw() { Console.WriteLine("Drawing a Shape"); } } |
The original program compiles and runs just as before. In real life, it does not make sense to define a method to draw a Shape because a Shape is an abstract concept and should be abstract. (It could even better be an interface.) However, this sample shows the usage and syntax of the virtual modifier.
A volatile keyword indicates that the variable can be modified by the hardware, OS, or concurrently running thread. When a variable is marked as volatile, the system always reads the current value of the variable at the point it is requested. If a variable is not volatile, it is possible that the CLR might decide that it is more efficient to use a cached value instead of reading a fresh copy. In addition, a volatile variable is written immediately. Here is how you would declare a volatile field:
public volatile int i;
Much like the do statement covered previously, this keyword declares a loop that executes until the expression evaluates to false. Listing A.56 illustrates a while statement.
public static void Main() { string input; bool stop = false; Console.WriteLine("Starting loop."); while(!stop) { Console.Write("Input . . ."); input = Console.ReadLine(); if(input[0] == 'B') stop = true; } } |
3.145.111.125