Professional Documents
Culture Documents
0]
note: this lesson covers new functionality in c# 2.0, which at the time of writing is not
released.
printer friendly version
in the original c# language, to construct a delegate object you have to provide it with the
name of a method. the code below shows an example: ‘print’ is first defined as a particular
delegate type, then its instance ‘delegatevariable’ is created by passing it the name of the
method ‘realmethod’.
1.
delegate void print (string s);
2.
print delegatevariable = new print(realmethod);
3.
4.
public void realmethod (string mystring)
5.
{
6.
messagebox.show(mystring);
7.
}
now, however, c# has been updated to include ‘anonymous’ methods (which should be pretty
easy for anyone who has used anonymous functions in languages like javascript). these allow
you to construct a delegate by specifying, rather than just naming a method. the following
gives an example of how the above code could be written using an anonymous method:
1.
public delegate void print (string s);
2.
print delegatevariable = delegate(string mystring) {messagebox.show(mystring);};
in the above case, the anonymous method is given the same signature as the delegate it is
passed to. but this is not always necessary. for the anonymous method to be acceptable, the
following two conditions must be met:
1. either the anonymous method has a parameter list that exactly matches the delegate’s
parameters; or the anonymous method has no parameter list and the delegate has no ‘out’
parameters.
2. either the values returned by the anonymous method are all of the right type for the
delegate; or the anonymous method doesn’t return anything and the delegate’s return type is
‘void’.
an implication of the first condition is that an anonymous method with no parameters at all
can fit a delegate with parameters. the following code is thus possible (notice that we’ve had
to remove the use of ‘mystring’ in the show method, because we’re no longer passing it in):
1.
public delegate void print (string s);
2.
print delegatevariable = delegate {messagebox.show(“hello world!”);};
outer types
anonymous methods can also make use of the local variables and parameters in whose scope
the anonymous method lies. this is a somewhat complicated, and we don’t yet have a clear
idea of when one should exploit it. so to illustrate it we’ll borrow the example from the
documentation.
1.
delegate int mydelegate();
2.
class test
3.
{
4.
static mydelegate myfunc()
5.
{
6.
int x=0;
7.
mydelegate result = delegate {return ++x;}
8.
return result;
9.
}
10.
11.
static void main()
12.
{
13.
mydelegate d = myfunc();
14.
console.writeline(d());
15.
console.writeline(d());
16.
console.writeline(d());
17.
}
18.
}
here the delegate ‘result’ declared in the function ‘myfunc’ makes use of (in the jargon,
captures) the integer ‘x’ which is declared in the same function. now, if we run the code, the
output is this:
1
2
3
this result is somewhat surprising, since the integer ‘x’ is instantiated and its value maintained
across three delegate calls after the function myfunc has finished running. how this is
described in the documentation is that the lifetime of the captured outer type is ‘extended’, so
that it lasts as long as any delegate that references it.
a further point to note is that in the example above each delegate is clearly referencing the
same type instance. but there are situations in which local variables get initialized many times
within a function, and these variables will count as different type instances. for example, in the
following loop, the integer i is initialised three times, and the delegates added into the
mydelegatearray will reference different variables.
1.
mydelegate[] mydelegatearray = new mydelegate[3];
2.
for (int x=0; x<3; x++)
3.
{
4.
int i = x;
5.
mydelegatearray[x] = delegate {return ++i};
6.
}
one question that this naturally raises is whether the multiple initialisation of i gives a
performance hit, which one could avoid by declaring i outside the loop. but the documentation
suggests that this isn’t the case; each new instance of i just slots neatly into the place vacated
by the previous one.
all tutorials
c# tutorial
the c# tutorial
contents
1 .net framework
2 c# vs c++/java
3 'hello world'
4 variable types
5 pointers
6 arrays
7 enumerations
8 operators
9 loops
10 jump/selection
11 classes...
12 ...declaration
13 methods
14 polymorphism
15 constants...
16 delegates...
17 exceptions
18 compiler
19 documentation
20 references
c# books (int)
c# books (uk)
patchwork book
21 [2.0] generics
23 [2.0] iterators
24 [2.0] partial...
25 [2.0] nullable...
c# tutorial lesson 23: iterators [2.0]
printer friendly version
note: this lesson covers new functionality in c# 2.0, which at the time of writing is not
released.
to understand iterators we first need to understand enumerators.
enumerators are specialist objects which provide one with the means to move through an
ordered list of items one at a time (the same kind of thing is sometimes called a ‘cursor’). the
.net framework provides two important interfaces relating to enumerators: ienumerator and
ienumerable. objects which implement ienumerator are themselves enumerators; they support
the following members:
- the property current, which points to a position on the list
- the method movenext, which moves the current item one along the list
- the method reset, which moves the current item to its initial position (which is before the
first item).
objects which implement ienumerable, on the other hand, merely contract to provide
enumerators when a request is made to their getenumerator method (excitingly, an object
that implements both of these interfaces can return itself from the getenumerator method!)
with the onset of generics, there are now also the corresponding generic interfaces
ienumerator<t> and ienumerable<t>.
the other fact worth mentioning is that the ‘foreach’ statement can be used to iterate through
an ienumerable or an ienumerator instance. this is illustrated by the following code, which
uses the arraylist class (which implements ienumerable).
1.
arraylist arr = new arraylist();
2.
arr.add(obj1);
3.
arr.add(obj2);
4.
5.
foreach (object o in arr)
6.
{
7.
messagebox.show(o.tostring());
8.
}
the point of iterators is to allow the easy implementation of enumerators. where a method
needs to return either an enumerator or an enumerable class for an ordered list of items, it is
written so as to return each item in its correct order using the ‘yield’ statement. the following
code demonstrates this idea:
1.
public ienumerable getenumerator()
2.
{
3.
for (int x=0; x<itemarray.length; x++)
4.
yield return itemarray[x];
5.
}
note that the code author doesn’t create any enumerators or enumerables within the code; he
just ‘yields’ the outputs in the required order and lets the compiler take care of generating the
appropriate object.
in this example, the type of the objects listed by the generated enumerator is 'object'. where
one is using a generic interface like ienumerable<t>, the type of the objects listed by the
enumerator is 't'.
returning a subset of items
the example method given previously demonstrates the ‘yield return’ statement. there is also
the ‘yield break’ statement, which is used to indicate that the last item has been yielded. this
statement could be used to limit the number of items in the enumerator vended by the
method, as in the following example (which lacks some basic checks on array sizes):
1.
public ienumerable getshortenumerator(int l)
2.
{
3.
for (int x=0; x<itemarray.length; x++)
4.
{
5.
yield return itemarray[x];
6.
if (x==l)
7.
yield break;
8.
}
9.
}
2.
{
3.
public void method1()
4.
{...}
5.
}
2.
{
3.
public void method2()
4.
{...}
5.
}
then the compiled object will exhibit both method1 and method2. note that it’s important that
the various aspects of the declaration like modifiers, type parameters, etc. all match up across
the multiple partial declarations.
the stated reason for introducing the ‘partial’ modifier is that it’s fairly common for projects to
include some automated code generation. partial types supports code generation because it
means that code changes won’t necessarily be overwritten if code generation occurs anew; the
changes can be held in distinct files.
generally speaking, it is a compile-time error to declare two elements of the same class twice.
the only exceptions are for things like inner classes, which may themselves be declared as
partial.
2.
this.outputwindow.text += (string) arrlist[x];
notice in the above that there is an explicit cast to a string when pulling out the string element
from the arraylist. this is because arraylists, in order to be of general use, store their elements
as objects.
but this isn’t an ideal situation when all you want to add to the arraylist is strings. to make a
runtime cast isn’t very efficient, as it involves some background checks that the cast is valid.
what would be better would be if the arraylist could be declared as a string-only arraylist, so
that all type-safety checks could be run at compile time.
this is the kind of functionality provided by generics. when you declare a generic class you
specify one or more ‘type parameters’ (which comprise the ‘flavours’ we appealed to at the
start of this lesson). these type parameters then constrain the class instance in a way that
compilers can verify.
suppose that we wanted a generic version of the arraylist class, which could be declared as
interested only in strings. we might implement this using an internal array (though of course
this may not be the best way to do it), and a partial specification of the class would look
something like:
1.
public class arraylist<t>
2.
{
3.
private t[] itemarray;
4.
5.
public void add(t newitem)
6.
{...}
7.
8.
public t this[int i]
9.
{...}
10.
}
note how the type parameter t is included in angle brackets after the class name, and is used
throughout the class definition wherever the definable type is needed.
an instance of this generic arraylist class – what is called a (closed) constructed type – could
then be declared and used with code like the following, which replaces the type parameters
with type arguments:
1.
arraylist<string> strarrlist = new arraylist<string>();
2.
strarrlist.add(“fred”);
3.
string str = strarrlist[0];
after the constructed type has been set up, there is no need to cast the elements that are
added into or removed from it; these have to be strings, and failure to comply with this
requirement results in compile time errors.
note also that it is not just standard classes that can take generic forms; there can also be
generic structs, interfaces and delegates.
multiple type parameters
the example class given above used only one type parameter. but a generic class can have any
number of type parameters, which are separated both in the class definition and the instance
declaration with commas inside the angle brackets. the declaration of such a class might look
like this:
1.
public mygenericclass<t,u>
2.
{…}
all the examples in the draft literature use a capital letter to indicate a type parameter, so this
usage should be taken as good practice. however, since type parameters are named using
standard identifiers, there is no formal requirement to use capital letters (this is also an
implication, of course, of the fact that there is no limit to the number of type parameters a
class may have).
it is possible to have classes with the same name but different numbers of class parameters
(note that it is the number of class parameters, not their identifiers that is important). so, for
instance, you could have both the following classes declared within the same namespace:
1.
public mygenericclass<t>
2.
public mygenericclass<t,u>
but not these:
1.
public mygenericclass<t,u>
2.
public mygenericclass<v,w>
generic methods
standard, non-generic classes can have generic methods, which are methods that are declared
with type parameters. both the inputs and the outputs of these methods may reference the
type variables, allowing code such as:
1.
public t mymethod<t>(mygenericclass<t>) {}
overloading occurs on methods analogously to the way it occurs on classes; the number of
type parameters a method has is used to distinguish it from other methods.
it is possible to call generic methods without actually giving a type argument; this relies upon
a process of type inference. for example, the method given above could be called using code
like:
1.
mygenericclass<int> myg = new mygenericclass<int>();
2.
int i = mymethod(myg);
type inference involves the compiler working out which type argument must have been meant
given the way the method was invoked. it seems dubious to us, however, that the brevity it
provides outweighs the clarity of leaving in the type argument.
note that while there are generic forms of methods, there are no generic forms of properties,
nor of events, indexers and operators.
type constraints
until now, we have implicitly assumed that any type argument may be provided for any type
parameter. but it is possible to restrict the range of possible values for each type parameter by
specifying constraints.
the following code comprises the header of a definition for the generic class mygenericclass.
the two ‘where’ clauses (which are placed on separate lines for readability only) provide the
constraints. the first clause restricts the first type parameter t to types which are - or which
sub-class - the myconstraint class. the second clause extends this constraint on u to
myconstraint types which also satisfy the myinterface interface.
1.
public class mygenericclass<t,u>
2.
where t: myconstraint
3.
where u: myconstraint, myinterface
4.
{...}
note that a single constraint can mention any number of interfaces, but a maximum of one
class.
why might we want to place a constraint on a type parameter? well, suppose that we wanted
to implement a bubble-sort routine in the ‘sort’ method for our generic arraylist class. in this
case it would be useful to restrict the types we’re dealing with to those which implement the
icomparable interface (which ensures that any two elements of the same type can be ranked).
this allows us to use the ‘compareto’ method without having to make any runtime casts or trap
errors, as shown by the following code.
1.
public class arraylist<t> where t: icomparable
2.
{
3.
private t[] itemarray;
4.
public void sort()
5.
{
6.
for (int x=1; x<itemarray.length; x++)
7.
{
8.
for (int y=0; y<x; y++)
9.
{
10.
int z = x-y;
11.
t itemhigher = itemarray[z];
12.
t itemlower = itemarray[z-1];
13.
if (itemlower.compareto(itemhigher)>0)
14.
{
15.
itemarray[z] = itemlower;
16.
itemarray[z-1] = itemhigher;
17.
}
18.
}
19.
}
20.
}
21.
}
2.
/// the myclass class represents an arbitrary class
3.
/// </summary>
4.
public class myclass
you are at liberty to use any xml tags you wish to document the code – as long as they follow
the xml syntax then the compiler will happily write them into the documentation. but microsoft
does provide a list of recommended xml elements for you to use. some of these elements
indicate the type of documentary information that is being given, and the compiler will validate
certain aspects of these. other elements are just used to give layout or formating information.
the following lists describe the main documentation elements provided. note that the content
of each element should be written between its opening and closing tags, and some of the tags
also take further attributes. in particular, the ‘cref’ attribute can supposedly be used in any
element, but we have just used it in the cases where it seems particularly appropriate.
tag(s) description
<summary> - holds overview information about any documentable element.
<remarks> - allows for expanded comments about any documentable element,
following summary information.
note: we still aren’t sure if the descriptions of the tags above are correct. the following points
describe the problem.
in favour of using the 'summary' and 'remarks' in the suggested way is the fact that
gunnarson, who helped create c#, sets things out in this way. it also correlates with the
default behaviour of visual studio.net, where ‘summary’ tags are given by default whenever
you start documenting any element.
on the other hand, in the help files of the (production) version of .net – v. 1.0.3705 – it
explicitly states that the use of ‘summary’ is to hold overview information about a class
member, whereas the use of ‘remarks’ is to hold overview information about a class. however,
some of the examples given throughout these same help files conflicts with this advice - for
example, in the description of the ‘paramref’ element, a class method is documented only with
‘remarks’. unfortunately, of course, this example also conflicts with what we say, since the
example contains no ‘summary’ tag.
basically, it’s all been knocked together by a committee of rather befuddled monkeys. but the
way we suggest is as good as any.
tag(s) description
<param - describes a parameter passed to a method. the compiler
name="name"> checks that the ‘name’ value matches an actual parameter in
the code. also, if you give documentation for one parameter
value, the compiler will expect you to give documentation for
them all.
<paramref - identifies the mention of a parameter name within some
name="name"> other descriptive element, for instance within ‘summary’ tags.
the idea is that this mentioned name can be styled differently
from the surrounding text. the compiler checks that the
‘name’ value matches an actual parameter in the code.
<returns> - describes the return value for a method. as the descriptive
field is just free text there is no compiler checking.
<exceptions - describes an exception that may be thrown by a method. the
cref="type"> ‘cref’ attribute refers to a type or field (such as
system.exception), and the compiler checks that this
reference is available from the current compilation
environment.
<permission - describes a permission requirement for a type or member.
cref="type"> the cref attribute refers to a type or field (such as
system.security.permissionset), and the compiler checks that
this reference is available from the current compilation
environment.
<value> - describes a class property.
<example> - gives an example of the use of the referenced object (for
example a class method). the ‘example’ element is often used
with the following two elements.
<c> - marks up a single phrase or line as a code example.
generally used in conjuction with the ‘example’ element.
<code> - marks up multiple lines as code examples. generally used in
conjuction with the ‘example’ element.
<see cref - used to identify a cross-reference in the documentation;
="type"> designed to be used inline within a description element. the
cref attribute refers to a type or field, and the compiler checks
that this reference is available from the current compilation
environment. and the see also tag is used in a separate
section. this allows the documentation to create cross-
references.
<seealso cref - used to identify a cross-reference in the documentation;
="type"> different from ‘see’ in that it is designed to stand alone. the
cref attribute refers to a type or field, and the compiler checks
that this reference is available from the current compilation
environment.
the following elements are just used to provide layout information:
<para> - used within descriptive tags like ‘remarks’, ‘summary’, etc. to
wrap a single paragraph.
<list type = - top level tags for a list, where this may be one of the three
”bullet” | types shown.there are more elements associated with the list
“number” | tag: the following code gives an example of these.
“table”>
<list type="table">
<listheader>
<term>animal</term>
<description>type</description>
</listheader>
<item>
<term>monkey</term>
<description>hairy</description>
</item>
<item>
<term>pig</term>
<description>bald</description>
</item>
</list>
note - in relation to the example of the 'list' tag given above - that the v. 1.0.3705 help
documentation for the enclosed 'item' tag talks about a ‘text’ element in place of the second
‘description’. but this seems to be just wrong.
generating c# documentation
you tell the compiler to produce documentation when compiling by invoking it with the switch:
/doc:file
in this switch, file represents the name of the file that you want the documentation written to.
as the documentation is generated in xml format, the file should have the extension .xml. so,
for instance, to produce the documentation for a program in sys.cs file in a file named my.xml,
we would use the command:
csc sys.cs /doc:my.xml
those working with visual studio .net should set the ‘xml documentation file’ property in the
‘build’ directory of the project’s configuration properties.
problems with the c# documenter
the c# documenter is lacking in many different areas. here are just a couple of problems that
make the documenter hard to live with.
firstly, if you don’t document a method, the documenter just ignores it (or else throws a
warning). this forces you to document all methods, no matter how simple and obvious they
might be, which is annoying when the computer could easily do it for you.
secondly, while the compiler checks the parameter types of methods, even to the extent of
providing the fully qualified parameter name, it fails to do this with the return values of
methods. the user has to manually insert the return type, even though the compiler could
easily produce this.
there is a solution, however...
the softsteel documenter
driven by documentation frustration, the softsteel team (well, andy really, but we all chipped
in with biscuits), has produced a command-line documenter, written in c#. it’s quite rusty at
the moment, but is already a whole load better than the inbuilt documenter. we’re working on
improvements, but we’re releasing it as free software under the gnu gpl in order to encourage
community support.
the downloads for the documentation tool are now being handled by the download page
http://www.softsteel.co.uk/tutorials/csharp/download.asp
2.
preprocessor test
3.
*/
4.
5.
#define myvar
6.
public class pretest
7.
{
8.
public static void main()
9.
{
10.
#if myvar
11.
print ("hello");
12.
#endif
13.
print ("andy");
14.
}
15.
}
in the above, the #define statement on line 5 acts as a boolean: it sets the variable myvar to
be 'defined' rather than 'undefined'. because it is defined, the code on line 16 gets compiled. if
we were to remove the statement on line 5, the compiler would effectively treat the code on
line 11 as commented out.
note: in the previous version of this page we followed the msdn documentation in using as our
example the variable debug. but it has been pointed out to us by a correspondent that the
debug variable is already defined by visual studio.net when a project is compiled as 'debug'
rather than 'release'. so if you're building from vs.net you wouldn't want to explicitly redefine
the variable debug like this.
the following gives a list of some of the available preprocessor directives.
directive action
#define symbol sets symbol to be 'defined' (true)
#undef symbol sets symbol to be 'undefined' (false)
#if symbol the if statement evaluates the given expression. the possible
[operator operators can be ==, !=, &&, ||. if the expression evaluates
symbol2] to 'true', the code to the #else, #elif or #endif directive is
compiled.
#else used in conjunction with the if statement.
#elif used in conjunction with the if statement as 'else-if'.
#endif ends the previous conditional directives
#warning text the given text appears as a warning in the compiler output
#error text the given text appears as an error in the compiler output
#line outputs a line number, and (optionally) a filename to the
number[file] compiler output.
#region name marks the beginning of a region
#end region marks the ends of a region
attributes
attributes are used to give extra information to the .net compiler. c# and the .net framework
have a few built-in attribute types, but it is also possible to create new ones by extending the
system.attribute class. below we describe a few common uses of attributes.
it is possible to tell the compiler that a class is compliant with the .net common language
specification (discussed in lesson 1) with code like the following:
1.
using system;
2.
3.
[clscompliant(true)]
4.
public class myclass
5.
{
6.
// class code
7.
}
similar code can also be used to indicate that a class has been obsoleted.
web services (mentioned in lesson 1) also make heavy use of attributes. demonstrated by the
example below, the attribute [ webmethod ] is used to specify that a particular method is to
be exposed as a web service.
1.
[ webmethod ]
2.
public int add(int num1, int num2)
3.
{
4.
return num1+num2;
5.
}
2.
{
3.
int zero = 0;
4.
res = (num / zero);
5.
}
6.
catch (system.dividebyzeroexception e)
7.
{
8.
console.writeline("error: an attempt to divide by zero");
9.
}
you can specify multiple catch blocks (following each other), to catch different types of
exception. a complication results, however, from the fact that exceptions form an object
hierarchy, so a particular exception might match more than one catch box. what you have to
do here is put catch boxes for the more specific exceptions before those for the more general
exceptions. at most one catch box will be triggered by an exception, and this will be the first
(and thus more specific) catch box reached.
following the last 'catch' box you can also include a 'finally' box. this code is guaranteed to run
whether or not an exception is generated. it is especially useful for cleanup code where this
would be skipped in the 'try' box following an exception being thrown.
where an exception is not caught by any of the subsequent 'catch' boxes, the exception is
thrown upwards to the code which called the method in which the exception occurred (note
that in c# the methods do not declare what exceptions they are throwing). this exception will
keep on bubbling upwards until it is either caught by some exception handling in the code, or
until it can go no further and causes the program to halt.
note that the exceptions a program throws need not be limited to those automatically
generated. a program can throw exceptions - including customised exceptions - whenever it
wishes, using the 'throw' command. the code below gives examples of all the statements
discussed above, with the 'getexception' method showing how to throw an exception.
1.
using system;
2.
public class exceptiondemo
3.
{
4.
public static void main ()
5.
{
6.
try
7.
{
8.
getexception();
9.
}
10.
catch (exception e)
11.
{
12.
console.writeline("we got an exception");
13.
}
14.
finally
15.
{
16.
console.writeline("the end of the program");
17.
}
18.
}
19.
20.
public static void getexception()
21.
{
22.
throw new exception();
23.
}
24.
}
2.
{
3.
// method code
4.
}
another method in this class could then instantiate the 'print' delegate in the following way, so
that it holds a reference to 'realmethod':
print delegatevariable = new print(realmethod);
we can note two important points about this example. firstly, the unqualified method passed to
the delegate constructor is implicitly recognised as a method of the instance passing it. that is,
the code is equivalent to:
print delegatevariable = new print(this.realmethod);
we can, however, in the same way pass to the delegate constructor the methods of other class
instances, or even static class methods. in the case of the former, the instance must exist at
the time the method reference is passed. in the case of the latter (exemplified below), the
class need never be instantiated.
print delegatevariable = new print(exampleclass.examplemethod);
the second thing to note about the example is that all delegates can be constructed in this
fashion, to create a delegate instance which refers to a single method. however, as we noted
before, some delegates - termed 'multicast delegates' - can simultaneously reference multiple
methods. these delegates must - like our print delegate - specify a 'void' return type.
one manipulates the references of multicast delegates by using addition and subtraction
operators (although delegates are in fact immutable reference types - for explanation of the
apparent contradiction see the discussion of strings in lesson 4). the following code gives some
examples:
1.
print s = null;
2.
s = s + new print (realmethod);
3.
s += new print (otherrealmethod);
the - and -= operators are used in the same way to remove method references from a
delegate.
the following code gives an example of the use of delegates. in the main method, the print
delegate is instantiated twice, taking different methods. these print delegates are then passed
to the display method, which by invoking the print delegate causes the method it holds to run.
as an exercise, you could try rewriting the code to make print a multicast delegate.
1.
using system;
2.
using system.io;
3.
4.
public class delegatetest
5.
{
6.
public delegate void print (string s);
7.
8.
public static void main()
9.
{
10.
print s = new print (toconsole);
11.
print v = new print (tofile);
12.
display (s);
13.
display (v);
14.
}
15.
16.
public static void toconsole (string str)
17.
{
18.
console.writeline(str);
19.
}
20.
21.
public static void tofile (string s)
22.
{
23.
streamwriter fileout = file.createtext("fred.txt");
24.
fileout.writeline(s);
25.
fileout.flush();
26.
fileout.close();
27.
}
28.
29.
public static void display(print pmethod)
30.
{
31.
pmethod("this should be displayed in the console");
32.
}
33.
}
events
to recap: in object-oriented languages, objects expose encapsulated functions called methods.
methods are encapsulated functions which run when they are invoked.
sometimes, however, we think of the process of method invocation more grandly. in such a
case, the method invocation is termed an 'event', and the running of the method is the
'handling' of the event. an archetypal example of an event is a user's selection of a button on
a graphical user interface; this action may trigger a number of methods to 'handle' it.
what distinguishes events from other method invocations is not, however, that they must be
generated externally. any internal change in the state of a program can be used as an event.
rather, what distinguishes events is that they are backed by a particular 'subscription-
notification' model. an arbitrary class must be able to 'subscribe to' (or declare its interest in)
a particular event, and then receive a 'notification' (ie. have one of its methods run) whenever
the event occurs.
delegates - in particular multicast delegates - are essential in realizing this subscription-
notification model. the following example describes how class 2 subscribes to an event issued
by class 1.
1. class 1 is an issuer of e-events. it maintains a public multicast delegate d.
3. when class 1 wants to issue an e-event, it calls d. this invokes all of the
methods which have subscribed to the event, including m.
the 'event' keyword is used to declare a particular multicast delegate (in fact, it is usual in the
literature to just identify the event with this delegate). the code below shows a class
eventissuer, which maintains an event field myevent. we could instead have declared the
event to be a property instead of a field (for the difference between these see lesson 15). to
raise the myevent event, the method onmyevent is called (note that we are checking in this
method to see if myevent is null - trying to trigger a null event gives a run-time error).
1.
public class eventissuer
2.
{
3.
public delegate void eventdelegate(object from, eventargs
args);
4.
public event eventdelegate myevent;
5.
6.
protected virtual void onmyevent(eventargs args)
7.
{
8.
if (myevent!=null)
9.
myevent(this, args);
10.
}
11.
a class which wanted to handle the events issued by an eventissuer ei with its method
handleevents would then subscribe to these events with the code:
ei.myevent += new eventissuer.eventdelegate(handleevents);
2.
{
3.
console.writeline("hello");
4.
}
5.
2.
{
3.
console.writeline("hello");
4.
base.onmyevent(args);
5.
}
6.
fields
fields are variables associated with either classes or instances of classes. there are seven
modifiers which can be used in their declarations. these include the four access modifiers
'public', 'protected', 'internal' and 'private' (discussed in lesson 12) and the 'new' keyword
(discussed in lesson 14). the two remaining modifiers are:
static
by default, fields are associated with class instances. use of the 'static' keyword, however,
associates a field with a class itself, so there will only ever be one such field per class,
regardless of the number of the class instances (and the static field will exist even if there are
no class instances). a static field need not be constant, however; it can be changed by code. in
the following code example the 'setstaticfield' method illustrates such a change.
1.
public class myclass
2.
{
3.
public static int staticfield = 1;
4.
5.
public myclass()
6.
{}
7.
8.
public void setstaticfield(int i)
9.
{
10.
myclass.staticfield = i;
11.
}
12.
}
readonly
where a field is readonly, its value can be set only once, either in the class declaration, or (for
non-static fields only) in the class constructor. the following code example (which, please note,
deliberately doesn't compile) shows both cases: the field staticreadonlyint is set in the class
declaration; the field readonlystring is set in the class constructor.
1.
public class myclass
2.
{
3.
public static readonly int staticreadonlyint = 1;
4.
public readonly string readonlystring;
5.
6.
public myclass()
7.
{
8.
readonlystring = "test";
9.
}
10.
11.
// this method doesn't compile
12.
public void notcompile()
13.
{
14.
myclass.staticreadonlyint = 4;
15.
this.readonlystring = "test2";
16.
}
17.
}
while we're on declarations, note also that a field declaration can involve multiple fields, as in
the following line of code
public static int a = 1, b, c = 2;
which is equivalent to
public static int a = 1;
public static int b;
public static int c = 2;
constants
constants are unchanging types, associated with classes, that are accessible at compile time.
because of this latter fact, constants can only be value types rather than reference types.
constant declarations take the 'const' keyword (not 'static', even though they are associated
with classes), and the five modifiers 'public', 'protected', 'internal', 'private' and 'new'.
the following is a simple constant declaration, although multiple constants can be
simultaneously declared.
public const int area = 4;
if you've been reading carefully, you may be struck by the thought: what's the difference
between declaring a field as 'const' and declaring a field 'static readonly'. good question. i'll
leave it to the professionals to provide the definitive answer, but the general point is that
static readonly fields can be reference types as well as value types.
properties
properties can be thought of as 'virtual' fields. from the outside, a class' property looks just
like a field. but from the inside, the property is generated using the actual class fields.
property declarations take just those modifiers taken by methods (see lesson 13) unlike
languages like java, c# provides dedicated support for accession and mutation of these
properties. suppose, for instance, that a type contains an internal field called 'age'. with the
following code one could specify a property age, providing accessors and mutators to this
internal field.
1.
public int age
2.
{
3.
get
4.
{
5.
return this.age;
6.
}
7.
set
8.
{
9.
this.age = value;
10.
}
11.
}
notice that the term 'value' is used in the above piece of code. this variable always holds the
value passed to the 'set' block. for instance, the execution of the following line of code
(assuming the appropriate class instance) would automatically set 'value' in the 'set' block to
4.
person.age = 4;
this property age can be described as 'read-write' since it can be both read from and written
to. to make a property 'write-only' one simply does not specify a 'get' block; to make it 'read-
only' one does not specify a 'set' block. the following piece of code demonstrates the read-only
property 'adult':
1.
public bool adult
2.
{
3.
get
4.
{
5.
if (this.age<18)
6.
return false;
7.
else
8.
return true;
9.
}
10.
}
indexers
if properties are 'virtual fields', indexers are more like 'virtual arrays'. they allow a class to
emulate an array, where the elements of this array are actually dynamically generated by
function calls.
the following piece of code defines a class to hold a list of runners in an athletics race. the
runners are held in lane order, and an indexer is exposed which allows the list to be both read
from and written to. the indexer deals gracefully with cases in which the lane number passed
to it is either too high or too low.
1.
class racedetails
2.
{
3.
private string[] lanes;
4.
5.
public racedetails()
6.
{
7.
this.lanes = new string[8];
8.
}
9.
10.
public string this[int i]
11.
{
12.
get
13.
{
14.
return (i>=0 && i<8) ? this.lanes[i] : "error";
15.
}
16.
17.
set
18.
{
19.
if (i>=0 && i<8) this.lanes[i] = value;
20.
}
21.
}
22.
}
the following simple code illustrates use being made of the class just defined. the name of the
person in the race's first lane is set, and then this name is sent to a console window.
1.
racedetails rd = new racedetails();
2.
rd[0] = "fred";
3.
console.writeline("lane one : " + rd[0]);
as can be seen from the example, an indexer is defined in a similar way to a property. one
important difference is in the indexer's signature; the word 'this' is used in place of a name,
and after this word indexing elements are provided.
indexers aren't differentiated by name, and a class cannot declare two indexers with the same
signature. however, this does not entail that a class is limited to just one indexer. different
indexers can have different types and numbers of indexing elements (these being equivalent
to method parameters, except that each indexer must have at least one indexing element, and
the 'ref' and 'out' modifiers cannot be used).
because indexing elements are not limited to integers, the original description of indexers as
'virtual arrays' actually rather undersells them. for example, where the indexing elements
include strings, indexers present themselves more like hash tables.
the following code shows an implementation for the racedetails class of an indexer whose
indexing element is a string. using this indexer it is possible to refer to a lane using the name
of the person currently filling that lane.
1.
public string this[string s]
2.
{
3.
get
4.
{
5.
int lanenum = getcorrespondinglane(s);
6.
return (lanenum<0) ? "error" : this.lanes[lanenum];
7.
}
8.
9.
set
10.
{
11.
int lanenum = getcorrespondinglane(s);
12.
if (lanenum>=0) this.lanes[lanenum] = value;
13.
}
14.
}
15.
16.
private int getcorrespondinglane(string myname)
17.
{
18.
for (int x=0; x<lanes.length; x++)
19.
{
20.
if (myname==lanes[x]) return x;
21.
}
22.
return -1;
23.
}
the following piece of code gives an example of the kind of use one might make of this string
indexer.
1.
rd["fred"] = "jill";
2.
{
3.
return length * width;
4.
}
to override this method the square class would then specify the
overriding method with the 'override' keyword. for example:
1.
public override double getarea()
2.
{
3.
return length * length;
4.
}
2.
{
3.
return length * width;
4.
}
1.
public new double getarea() // in square
2.
{
3.
return length * length;
4.
}
note that a method can 'hide' another one without the access
modifiers of these methods being the same. so, for instance, the
square's getarea method could be declared as private, viz:
1.
private new double getarea()
2.
{
3.
return length * length;
4.
}
all tutorials
c# tutorial
the c# tutorial
contents
1 .net framework
2 c# vs c++/java
3 'hello world'
4 variable types
5 pointers
6 arrays
7 enumerations
8 operators
9 loops
10 jump/selection
11 classes...
12 ...declaration
13 methods
14 polymorphism
15 constants...
16 delegates...
17 exceptions
18 compiler
19 documentation
20 references
c# books (int)
c# books (uk)
patchwork book
21 [2.0] generics
23 [2.0] iterators
24 [2.0] partial...
25 [2.0] nullable...