Maharaja Institute of Technology Mysore Department of ISE
MODULE 4 [CHAPTER 1]
Implementing properties to access fields
Implementing encapsulation by using methods
First, will see the original motivation for using methods to hide fields.
Consider the following structure that represents a position on a computer screen as a pair
of coordinates, x and y. Assume that the range of valid values for the x-coordinate lies
between 0 and 1280, and the range of valid values for the y-coordinate lies between 0 and
1024.
struct ScreenPosition
{
public int X; public int Y;
public ScreenPosition(int x, int y)
{
this.X = rangeCheckedX(x);
this.Y = rangeCheckedY(y);
}
private static int rangeCheckedX(int x)
{
if (x < 0 || x > 1280)
{
throw new ArgumentOutOfRangeException(“X”);
}
return x;
}
private static int rangeCheckedY(int y)
{
if (y < 0 || y > 1024)
{
throw new ArgumentOutOfRangeException(“Y”);
}
return y;
}}
One problem with this structure is that it does not follow the golden rule of
encapsulation—that is, it does not keep its data private. Public data is often a bad idea
because the class cannot control the values that an application specifies.
For example, the ScreenPosition constructor range checks its parameters to ensure that
they are in a specified range, but no such check can be done on the “raw” access to the
public fields.
ScreenPosition origin = new ScreenPosition(0, 0);
...
DOT NET FRAMEWORK FOR APPLICATION DEVELOPMENT (17CS564) Page 106
Maharaja Institute of Technology Mysore Department of ISE
int xpos = origin.X;
origin.Y = -100; // oops
The common way to solve this problem is to make the fields private and add an accessor
method and a modifier method to respectively read and write the value of each private
field.
struct ScreenPosition
{
...
public int GetX()
{
return this.x;
}
public void SetX(int newX)
{
this.x = rangeCheckedX(newX);
}
...
private static int rangeCheckedX(int x) { ... }
private static int rangeCheckedY(int y) { ... }
private int x, y;
}
What are properties?
A property is a cross between a field and a method—it looks like a field but acts like a method.
You access a property by using exactly the same syntax that you use to access a field.
The syntax for a property declaration looks like this:
AccessModifier Type PropertyName
{
Get
{
// read accessor code
}
set
{
// write accessor code
}
}
A property can contain two blocks of code, starting with the get and set keywords. The
get block contains statements that execute when the property is read, and the set block
contains statements that run upon writing to the property. The type of the property
specifies the type of data read and written by the get and set accessors.
The next code example shows the ScreenPosition structure rewritten by using properties.
When looking at this code, notice the following:
DOT NET FRAMEWORK FOR APPLICATION DEVELOPMENT (17CS564) Page 107
Maharaja Institute of Technology Mysore Department of ISE
Lowercase _x and _y are private fields.
Uppercase X and Y are public properties.
All set accessors are passed the data to be written by using a hidden, built-in
parameter named value.
struct ScreenPosition
{
private int _x, _y;
public ScreenPosition(int X, int Y)
{
this._x = rangeCheckedX(X);
this._y = rangeCheckedY(Y);
}
public int X
{
get { return this._x; }
set { this._x = rangeCheckedX(value);
}
}
public int Y
{
get { return this._y; }
set { this._y = rangeCheckedY(value);
}
}
private static int rangeCheckedX(int x) { ... }
private static int rangeCheckedY(int y) { ... }
}
In this example, a private field directly implements each property, but this is only one
way to implement a property. All that is required is that a get accessor returns a value of
the specified type.
When you use a property in an expression, you can use it in a read context (when you are
retrieving its value) and in a write context (when you are modifying its value). The
following example shows how to read values from the X and Y properties of the
ScreenPosition structure:
ScreenPosition origin = new ScreenPosition(0, 0);
int xpos = origin.X; // calls [Link]
int ypos = origin.Y; // calls [Link]
Notice that you access properties and fields by using identical syntax. When you use a
property in a read context, the compiler automatically translates your field-like code into
a call to the get accessor of that property.
Similarly, if you use a property in a write context, the compiler automatically translates
your field-like code into a call to the set accessor of that property.
DOT NET FRAMEWORK FOR APPLICATION DEVELOPMENT (17CS564) Page 108
Maharaja Institute of Technology Mysore Department of ISE
origin.X = 40; // calls [Link], with value set to 40origin.
Y = 100; // calls [Link], with value set to 100
Read-only properties
You can declare a property that contains only a get accessor. In this case, you can use the
property only in a read context. For example, here’s the X property of the ScreenPosition
structure declared as a read-only property:
struct ScreenPosition
{
private int _x;
...
public int X
{
get { return this._x; }
}
}
The X property does not contain a set accessor; therefore, any attempt to use X in a write
context will fail, as demonstrated in the following example:
origin.X = 140; // compile-time error
Write-only properties
Similarly, you can declare a property that contains only a set accessor. In this case, you
can use the property only in a write context. For example, here’s the X property of the
ScreenPosition structure declared as a write-only property:
struct ScreenPosition
{
private int _x;
...
public int X
{
Set { this._x = rangeCheckedX(value); }
}}
The X property does not contain a get accessor; any attempt to use X in a read context will fail, as
illustrated here:
[Link](origin.X); // compile-time error
origin.X = 200; // compiles OK
origin.X += 10; // compile-time error
Property accessibility
You can specify the accessibility of a property (public, private, or protected) when you
declare it. However, it is possible within the property declaration to override the property
accessibility for the get and set accessors.
DOT NET FRAMEWORK FOR APPLICATION DEVELOPMENT (17CS564) Page 109
Maharaja Institute of Technology Mysore Department of ISE
For example, the version of the ScreenPosition structure shown in the code that follows
defines the set accessors of the X and Y properties as private. (The get accessors are
public, because the properties are public.)
struct ScreenPosition
{
private int _x, _y;
...
public int X
{
get { return this._x; }
private set { this._x = rangeCheckedX(value); }
}
public int Y
{
get { return this._y; }
private set { this._y = rangeCheckedY(value); }
}
...}
You must observe some rules when defining accessors with different accessibility from one
another:
You can change the accessibility of only one of the accessors when you define it. It
wouldn’t make much sense to define a property as public only to change the accessibility
of both accessors to private anyway.
The modifier must not specify an accessibility that is less restrictive than that of the
property. For example, if the property is declared as private, you cannot specify the read
accessor as public. (Instead, you would make the property public and make the write
accessor private.)
Understanding the property restrictions
You can assign a value through a property of a structure or class only after the structure
or class has been initialized. The following code example is illegal because the location
variable has not been initialized (by using new):
ScreenPosition location;
location.X = 40; // compile-time error, location not assigned
You can’t use a property as a ref or an out argument to a method (although you can use a
writable field as a ref or an out argument). This makes sense because the property doesn’t
really point to a memory location; rather, it points to an accessor method, such as in the
following example:
MyMethod(ref location.X); // compile-time error
A property can contain at most one get accessor and one set accessor. A property cannot
contain other methods, fields, or properties.
DOT NET FRAMEWORK FOR APPLICATION DEVELOPMENT (17CS564) Page 110
Maharaja Institute of Technology Mysore Department of ISE
The get and set accessors cannot take any parameters. The data being assigned is passed
to the set accessor automatically by using the value variable.
You can’t declare const properties, such as is demonstrated here:
const int X { get { ... } set { ... } } // compile-time error
Declaring interface properties
We specify the get or set keyword, or both, but replace the body of the get or set accessor
with a semicolon, as shown here:
interface IScreenPosition
{
int X { get; set; } int Y { get; set; }
}
Any class or structure that implements this interface must implement the X and Y
properties with get and set accessor methods.
struct ScreenPosition : IScreenPosition
{ ...
public int X { get { ... } set { ... } }
public int Y { get { ... } set { ... } }
...}
If you implement the interface properties in a class, you can declare the property
implementations as virtual, which enables derived classes to override the
implementations.
class ScreenPosition : IScreenPosition
{ ...
public virtual int X { get { ... } set { ... } }
public virtual int Y { get { ... } set { ... } }
}
You can also choose to implement a property by using the explicit interface
implementation
struct ScreenPosition : IScreenPosition
{ ...
int IScreenPosition.X { get { ... } set { ... } }
int IScreenPosition.Y { get { ... } set { ... } }
... }
Generating automatic properties
The principal purpose of properties is to hide the implementation of fields from the
outside world. However, there are at least two good reasons why you should define
properties rather than exposing data as public fields even in these situations:
DOT NET FRAMEWORK FOR APPLICATION DEVELOPMENT (17CS564) Page 111
Maharaja Institute of Technology Mysore Department of ISE
Compatibility with applications Fields and properties expose themselves by using
different metadata in assemblies. If you develop a class and decide to use public fields,
any applications that use this class will reference these items as fields. Although you use
the same C# syntax for reading and writing a field that you use when reading and writing
a property, the compiled code is actually quite different—the C# compiler just hides the
differences from you. If you later decide that you really do need to change these fields to
properties, existing applications will not be able to use the updated version of the class
without being recompiled. This is awkward if you have deployed the application on a
large number of users’ desktops throughout an organization.
Compatibility with interfaces If you are implementing an item as a property, you must
write a property that matches the specification in the interface, even if the property just
reads and writes data in a private field.
The designers of the C# language recognized that programmers are busy people who
should not have to waste their time writing more code than they need to. To this end, the
C# compiler can generate the code for properties for you automatically, like this:
class Circle
{
public int Radius{ get; set; }
...}
The C# compiler converts this definition to a private field and a default implementation
that looks similar to this:
class Circle
{
private int _radius;
public int Radius
{
get { return this._radius; }
set { this._radius = value; }
}
...}
We can implement a simple property by using automatically generated code, and if you
need to include additional logic later, you can do so without breaking any existing
applications.
Initializing objects by using properties
Object initializing using contractor: An object can have multiple constructors, and you
can define constructors with varying parameters to initialize different elements in an
object. For example, you could define a class that models a triangle, like this:
DOT NET FRAMEWORK FOR APPLICATION DEVELOPMENT (17CS564) Page 112
Maharaja Institute of Technology Mysore Department of ISE
public class Triangle
{
private int side1Length; private int side2Length; private int side3Length;
public Triangle()
{
this.side1Length = this.side2Length = this.side3Length = 10;
}
public Triangle(int length1)
{
this.side1Length = length1; this.side2Length = this.side3Length = 10;
}
public Triangle(int length1, int length2)
{
this.side1Length = length1; this.side2Length = length2; this.side3Length = 10;
}
public Triangle(int length1, int length2, int length3)
{
this.side1Length = length1; this.side2Length = length2; this.side3Length =
length3;
}
}
Problem with constructor :For example, in the preceding Triangle class, you could not
easily add a constructor that initializes only the side1Length and side3Length fields
because it would not have a unique signature;
One possible solution is to define a constructor that takes optional parameters and
specify values for the parameters as named arguments when you create a Triangle
object.
Another transparent solution is to initialize the private fields to a set of default values
and expose them as properties, like this:
public class Triangle
{
private int side1Length = 10;
private int side2Length = 10;
private int side3Length = 10;
public int Side1Length { set { this.side1Length = value; } }
public int Side2Length { set { this.side2Length = value; } }
public int Side3Length { set { this.side3Length = value; } }
}
When we create an instance of a class, you can initialize it by specifying the names and
values for any public properties that have set accessors.
Triangle tri1 = new Triangle { Side3Length = 15 };
Triangle tri2 = new Triangle { Side1Length = 15, Side3Length = 20 };
Triangle tri3 = new Triangle { Side2Length = 12, Side3Length = 17 };
Triangle tri4 = new Triangle { Side1Length = 9, Side2Length = 12, Side3Length = 15 };
DOT NET FRAMEWORK FOR APPLICATION DEVELOPMENT (17CS564) Page 113
Maharaja Institute of Technology Mysore Department of ISE
Using indexers[Chapter 2]
What is an indexer?
We can think of an indexer as a smart array in much the same way that you can think of a
property as a smart field. Whereas a property encapsulates a single value in a class, an indexer
encapsulates a set of values. The syntax that you use for an indexer is exactly the same as the
syntax that you use for an array.
The best way to understand indexers is to work through an example.
An example that doesn’t use indexers
C# provides a set of operators that you can use to access and manipulate the individual
bits in an int. These operators are as follows:
The NOT (~) operator This is a unary operator that performs a bitwise complement. For
example, if you take the 8-bit value 11001100 (204 decimal) and apply the ~ operator to
it, you obtain the result 00110011 (51 decimal)—all the 1s in the original value become
0s, and all the 0s become 1s.
The left-shift (<<) operator This is a binary operator that performs a left shift. The
expression 204 << 2 returns the value 48. (In binary, 204 decimal is 11001100, and left-
shifting it by two places yields 00110000, or 48 decimal.) The far-left bits are discarded,
and zeros are introduced from the right. There is a corresponding right-shift operator, >>.
The OR (|) operator This is a binary operator that performs a bitwise OR operation,
returning a value containing a 1 in each position in which either of the operands has a 1.
For example, the expression 204 | 24 has the value 220 (204 is 11001100, 24 is
00011000, and 220 is 11011100).
The AND (&) operator This operator performs a bitwise AND operation. AND is
similar to the bitwise OR operator, except that it returns a value containing a 1 in each
position where both of the operands have a 1. So, 204 & 24 is 8 (204 is 11001100, 24 is
00011000, and 8 is 00001000).
The XOR (^) operator This operator performs a bitwise exclusive OR operation, return-
ing a 1 in each bit where there is a 1 in one operand or the other but not both. (Two 1s
yield a 0—this is the “exclusive” part of the operator.) So 204 ^ 24 is 212 (11001100 ^
00011000 is 11010100).
(bits & (1 << 5)) != 0
Suppose that the bits variable contains the decimal value 42. In binary, this is 00101010.
The decimal value 1 is 00000001 in binary, and the expression 1 << 5 has the value
00100000; the sixth bit is 1. In binary, the expression bits & (1 << 5) is 00101010 &
00100000, and the value of this expression is binary 00100000, which is nonzero. If the
variable bits contains the value 65,
The trouble with these examples is that although they work, they are fiendishly difficult
to understand. They’re complicated, and the solution is a very low-level one: it fails to
create an abstraction of the problem that it solves, and it is consequently very difficult to
maintain code that performs these kinds of operations.
DOT NET FRAMEWORK FOR APPLICATION DEVELOPMENT (17CS564) Page 114
Maharaja Institute of Technology Mysore Department of ISE
The same example using indexers
The best way to solve above problem is to use an int as if it were an array of bits.
Eq:
bits[5] // access the bit 6 places from the right in the bits variable
bits[3] = true // set the bit 4 places from the right to true
We can’t use the square bracket notation on an int; it works only on an array or on a type
that behaves like an array. So, the solution to the problem is to create a new type that acts
like, feels like, and is used like an array of bool variables but is implemented by using an
int. You can achieve this feat by defining an indexer.
struct IntBits
{ private int bits;
public IntBits(int initialBitValue)
{ bits = initialBitValue; }
}
To define the indexer, you use a notation that is a cross between a property and an array.
You introduce the indexer with the this keyword, specify the type of the value returned
by the indexer, and also specify the type of the value to use as the index into the indexer
between square brackets.
public bool this [ int index ]
{
get
{
return (bits & (1 << index)) != 0;
}
set
{
if (value) // turn the bit on if value is true; otherwise, turn it off
bits |= (1 << index);
else
bits &= ~(1 << index);
}
}
Notice the following points:
An indexer is not a method; there are no parentheses containing a parameter, but there are
square brackets that specify an index. This index is used to specify which element is
being accessed.
All indexers use the this keyword. A class or structure can define at most one indexer
and it is always named this.
Indexers contain get and set accessors just like properties. In this example, the get and set
accessors contain the complicated bitwise expressions previously discussed.
DOT NET FRAMEWORK FOR APPLICATION DEVELOPMENT (17CS564) Page 115
Maharaja Institute of Technology Mysore Department of ISE
The index specified in the indexer declaration is populated with the index value specified
when the indexer is called. The get and set accessor methods can read this argument to
determine which element should be accessed.
After you have declared the indexer, you can use a variable of type IntBits instead of an
int and apply the square bracket notation, as shown in the next example:
int adapted = 126; // 126 has the binary representation 01111110
IntBits bits = new IntBits(adapted);
bool peek = bits[6]; // retrieve bool at index 6; should be true (1)
bits[0] = true; // set the bit at index 0 to true (1)
bits[3] = false; // set the bit at index 3 to false (0)
Understanding indexer accessors
When we read an indexer, the compiler automatically translates your array-like code into a
call to the get accessor of that indexer. Consider the following example:
bool peek = bits[6]; // call to the get accessor for bits, and the index argument is set to 6.
bits[3] = true; // call to the set accessor of that indexer, setting the index value to true
bits[6] ^= true; //This code is automatically translated into the following:
bits[6] = bits[6] ^ true; //calls the indexer declares both a get and a set accessor.
Comparing indexers and arrays
When we use an indexer, the syntax is deliberately array-like. However, there are some
important differences between indexers and arrays:
1. Indexers can use non-numeric subscripts, such as a string as shown in the following
example, whereas arrays can use only integer subscripts.
public int this [ string name ] { ... } // OK
2. Indexers can be overloaded (just like methods), whereas arrays cannot.
public Name this [ PhoneNumber number ]
{ ... }
public PhoneNumber this [ Name name ]
{ ... }
3. Indexers cannot be used as ref or out parameters, whereas array elements can.
IntBits bits; // bits contains an indexerMethod(ref bits[1]); // compile-time error
Indexers in interfaces
We can declare indexers in an interface. To do this, specify the get keyword, the set
keyword, or both, but replace the body of the get or set accessor with a semicolon. Any
class or structure that implements the interface must implement the indexer accessors
declared in the interface, as demonstrated here:
interface IRawInt
{
bool this [ int index ] { get; set; }
}
DOT NET FRAMEWORK FOR APPLICATION DEVELOPMENT (17CS564) Page 116
Maharaja Institute of Technology Mysore Department of ISE
struct RawInt : IRawInt
{ ...
public bool this [ int index ]
{ get { ... } set { ... } }
...}
Implement the interface indexer in a class, we can declare the indexer implementations as
virtual.
class RawInt : IRawInt
{ ...
public virtual bool this [ int index ]
{ get { ... } set { ... } }
...}
We can also choose to implement an indexer by using the explicit interface
implementation [Link] explicit implementation of an indexer is nonpublic and
nonvirtual (and so cannot be overridden), as shown in this example:
struct RawInt : IRawInt
{ ...
bool [Link] [ int index ]
{ get { ... } set { ... } }
...}
DOT NET FRAMEWORK FOR APPLICATION DEVELOPMENT (17CS564) Page 117
Maharaja Institute of Technology Mysore Department of ISE
MODULE 4[CHAPTER 3]
Introducing generics
The problem with the object type
DOT NET FRAMEWORK FOR APPLICATION DEVELOPMENT (17CS564) Page 118
Maharaja Institute of Technology Mysore Department of ISE
To understand generics, it is worth looking in detail at the problem for which they are
designed to solve. Suppose that we needed to model a first-in, first-out structure such as a
queue. We could create a class such as the above it.
This class uses an array to provide a circular buffer for holding the data. The size of this
array is specified by the constructor. An application uses the Enqueue method to add an
item to the queue and the Dequeue method to pull an item off the queue. The private head
and tail fields keep track of where to insert an item into the array and where to retrieve an
item from the array. The numElements field indicates how many items are in the array.
The Queue class works well for queues of ints, but what if Wewant to create queues of
strings, or floats, or even queues of more complex types such as Circle
Queue queue = new Queue();
Horse myHorse = new Horse();
[Link](myHorse); // error: Cannot convert from Horse to int
One way around this restriction is to specify that the array in the Queue class contains
items of type object, update the constructors, and modify the Enqueue and Dequeue
methods to take an object parameter and return an object, such as in the following:
Queue queue = new Queue();
Horse myHorse = new Horse();
[Link](myHorse); // Now legal – Horse is an object
DOT NET FRAMEWORK FOR APPLICATION DEVELOPMENT (17CS564) Page 119
Maharaja Institute of Technology Mysore Department of ISE
Disadvantage of using the object approach to create generalized classes and methods is
that it can consume additional memory and processor time if the runtime needs to convert
an object to a value type and back again. Consider the following piece of code that
manipulates a queue of int values:
Queue queue = new Queue();
int myInt = 99;[Link](myInt); // box the int to an object
...
myInt = (int)[Link](); // unbox the object to an int
The Queue data type expects the items it holds to be objects, and object is a reference
type. Enqueueing a value type, such as an int, requires it to be boxed to convert it to a
reference type. Similarly, dequeueing into an int requires the item to be unboxed to
convert it back to a value type.
The generics solution
C# provides generics to remove the need for casting, improve type safety, reduce the
amount of boxing required, and make it easier to create generalized classes and methods.
Generic classes and methods accept type parameters, which specify the types of objects
on which they operate.
class Queue<T>
{
...
}
DOT NET FRAMEWORK FOR APPLICATION DEVELOPMENT (17CS564) Page 120
Maharaja Institute of Technology Mysore Department of ISE
The T in this example acts as a placeholder for a real type at compile time. When Wewrite code
to instantiate a generic Queue, Weprovide the type that should be substituted for T (Circle,
Horse, int, and so on).
The following examples create a Queue of ints, and a Queue of Horses:
Queue<int> intQueue = new Queue<int>();
Queue<Horse> horseQueue = new Queue<Horse>();
The compiler now has enough information to perform strict type-checking when Webuild
the application. Weno longer need to cast data when we call the Dequeue method, and the
compiler can trap any type mismatch errors early:
[Link](99);
int myInt = [Link](); // no casting necessary
Horse myHorse = [Link](); // compiler error:
// cannot implicitly convert type 'int' to 'Horse'
Generics vs. generalized classes
It is important to be aware that a generic class that uses type parameters is different from
a generalized class designed to take parameters that can be cast to different types. For
example, the object-based version of the Queue class shown earlier is a generalized class.
There is a single implementation of this class, and its methods take object parameters and
return object types. Wecan use this class with ints, strings, and many other types, but in
each case, Weare using instances of the same class and Wehave to cast the data Weare
using to and from the object type.
DOT NET FRAMEWORK FOR APPLICATION DEVELOPMENT (17CS564) Page 121
Maharaja Institute of Technology Mysore Department of ISE
Compare this with the Queue<T> class. Each time Weuse this class with a type
parameter (such as Queue<int> or Queue<Horse>), Wecause the compiler to generate
an entirely new class that happens to have functionality defined by the generic class.
Generics and constraints
By using a constraint, Wecan limit the type parameters of a generic class to those that
implement a particular set of interfaces and therefore provide the methods defined by
those interfaces. For example, if the IPrintable interface defined the Print method,
Wecould create the PrintableCollection class like this:
public class PrintableCollection<T> where T : IPrintable
When Webuild this class with a type parameter, the compiler checks to ensure that the
type used for T actually implements the IPrintable interface; if it doesn’t, it stops with a
compilation error.
Creating a generic method
Generic methods are frequently used in conjunction with generic classes; we need them
for methods that take generic types as parameters or that have a return type that is a
generic type.
With a generic method, Wecan specify the types of the parameters and the return type by
using a type parameter in a manner similar to that used when defining a generic class. We
define generic methods by using the same type parameter syntax that Weuse when
creating generic classes. For example, the generic Swap<T> method in the code that
follows swaps the values in its parameters. Because this functionality is useful regardless
of the type of data being swapped, it is helpful to define it as a generic method:
static void Swap<T>(ref T first, ref T second)
{
T temp = first;
first = second;
second = temp;
}
Invoke the method by specifying the appropriate type for its type parameter. The
following examples show how to invoke the Swap<T> method to swap over two ints and
two strings:
int a = 1, b = 2;
Swap<int>(ref a, ref b);
...string s1 = "Hello", s2 = "World";
Swap<string>(ref s1, ref s2);
DOT NET FRAMEWORK FOR APPLICATION DEVELOPMENT (17CS564) Page 122
Maharaja Institute of Technology Mysore Department of ISE
Variance and generic interfaces
The Wrapper<T> class provides a simple wrapper around a specified type. The
IWrapper interface defines the SetData method that the Wrapper<T> class implements
to store the data and the GetData method that the Wrapper<T> class implements to
retrieve the data.
we can create an instance of this class and use it to wrap a string like this:
Covariant interfaces
Suppose that we defined the IStoreWrapper<T> and IRetrieveWrapper<T> interfaces,
shown in the following example, in place of IWrapper<T> and implemented these
interfaces in the Wrapper<T> class, like this:
DOT NET FRAMEWORK FOR APPLICATION DEVELOPMENT (17CS564) Page 123
Maharaja Institute of Technology Mysore Department of ISE
IRetrieveWrapper<object> retrievedObjectWrapper = stringWrapper; //error
The compiler does implicit conversions are legal and that it does not have to enforce
strict type-safety. We do this by specifying the out keyword when Wedeclare the type
parameter, like this:
This feature is called covariance. Wecan assign an IRetrieveWrapper<A> object to an
IRetrieveWrapper<B> reference as long as there is a valid conversion from type A to
type B, or type A derives from type B. The following code now compiles and runs as
expected:
// string derives from object, so this is now legal
IRetrieveWrapper<object> retrievedObjectWrapper = stringWrapper;
DOT NET FRAMEWORK FOR APPLICATION DEVELOPMENT (17CS564) Page 124
Maharaja Institute of Technology Mysore Department of ISE
Contravariant interfaces
Contravariance follows a similar principle to covariance except that it works in the
opposite direction; it enables Weto use a generic interface to reference an object of type B
through a reference to type A as long as type B derives type A. This sounds complicated,
so it is worth looking at an example from the .NET Framework class library.
The [Link] namespace in the .NET Framework provides an interface
called IComparer, which looks like this:
A class that implements this interface has to define the Compare method, which is used
to compare two objects of the type specified by the T type parameter. The Compare
method is expected to return an integer value: zero if the parameters x and y have the
same value, negative if x is less than y, and positive if x is greater than y.
We can create an ObjectComparer object and call the Compare method through the
IComparer<Object> interface to compare two objects, like this:
Covariance example If the methods in a generic interface can return strings, they can also return
objects. (All strings are objects.)
DOT NET FRAMEWORK FOR APPLICATION DEVELOPMENT (17CS564) Page 125
Maharaja Institute of Technology Mysore Department of ISE
Contravariance example If the methods in a generic interface can take object parameters, they
can take string parameters.
Creating a generic class
The theory of binary trees
A binary tree is a useful data structure that Wecan use for a variety of operations,
including sorting and searching through data very quickly.
A binary tree is a recursive (self-referencing) data structure that can either be empty or
contain three elements: a datum, which is typically referred to as the node, and two
subtrees, which are themselves binary trees. The two subtrees are conventionally called
the left subtree and the right subtree because they are typically depicted to the left and
right of the node, respectively.
Each left subtree or right subtree is either empty or contains a node and other subtrees. In
theory, the whole structure can continue ad infinitum. The following image shows the
structure of a small binary tree.
Binary tree construction
DOT NET FRAMEWORK FOR APPLICATION DEVELOPMENT (17CS564) Page 126
Maharaja Institute of Technology Mysore Department of ISE
Binary tree display
C# program for Btree using generic class and method:
DOT NET FRAMEWORK FOR APPLICATION DEVELOPMENT (17CS564) Page 127
Maharaja Institute of Technology Mysore Department of ISE
DOT NET FRAMEWORK FOR APPLICATION DEVELOPMENT (17CS564) Page 128
Maharaja Institute of Technology Mysore Department of ISE
MODULE 4[CHAPTER 4]
Using collections
The Microsoft .NET Framework provides several classes that collect elements together
such that an application can access them in specialized ways. they live in the
[Link] namespace.
As the namespace implies, these collections are generic types; they all expect We to
provide a type parameter indicating the kind of data that your application will be storing
in them. Each collection class is optimized for a particular form of data storage and
access, and each provides specialized methods that support this functionality.
For example, the Stack<T> class implements a last-in, first-out model, where We add an
item to the top of the stack by using the Push method, and Wetake an item from the top
of the stack by using the Pop method. The Pop method always retrieves the most recently
pushed item and removes it from the stack. In contrast, the Queue<T> type provides the
Enqueue and Dequeue methods The Enqueue method adds an item to the queue, whereas
the Dequeue method retrieves items in the same order and removes them from the queue.
The List<T> collection class
The generic List<T> class is the simplest of the collection classes. We can use it much
like an array—We can reference an existing element in a List<T> collection by using
ordinary array notation, with square brackets and the index of the element, although We
cannot use array notation to add new elements. However, in general, the List<T> class
DOT NET FRAMEWORK FOR APPLICATION DEVELOPMENT (17CS564) Page 129
Maharaja Institute of Technology Mysore Department of ISE
provides more flexibility than arrays and is designed to overcome the following
restrictions exhibited by arrays:
1) If We want to resize an array, We have to create a new array, copy the elements
(leaving out some if the new array is smaller), and then update any references to the
original array so that they refer to the new array.
2) If We want to remove an element from an array, We have to move all the trailing
elements up by one place. Even this doesn’t quite work, because We end up with two
copies of the last element.
3) If We want to insert an element into an array, We have to move elements down by
one place to make a free slot. However, We lose the last element of the array!
The List<T> collection class provides the following features that preclude these
limitations:
1) We don’t need to specify the capacity of a List<T> collection when We create it; it
can grow and shrink as We add elements. There is an overhead associated with this
dynamic behavior, and if necessary We can specify an initial size. However, if We
exceed this size, then the List<T> collection will simply grow as necessary.
2)We can remove a specified element from a List<T> collection by using the Remove
method. The List<T> collection automatically reorders its elements and closes the gap.
We can also remove an item at a specified position in a List<T> collection by using the
RemoveAt method.
3) We can add an element to the end of a List<T> collection by using its Add method.
We supply the element to be added. The List<T> collection resizes itself automatically.
4) We can insert an element into the middle of a List<T> collection by using the Insert
method. Again, the List<T> collection resizes itself.
5)We can easily sort the data in a List<T> object by calling the Sort method.
DOT NET FRAMEWORK FOR APPLICATION DEVELOPMENT (17CS564) Page 130
Maharaja Institute of Technology Mysore Department of ISE
The LinkedList<T> collection class
The LinkedList<T> collection class implements a doubly linked list. Each item in the list
holds the value for the item together with a reference to the next item in the list (the Next
property) and the previous item (the Previous property). The item at the start of the list
has the Previous property set to null, and the item at the end of the list has the Next
property set to null.
LinkedList<T> does not support array notation for inserting or examining elements.
Instead, Wecan use the AddFirst method to insert an element at the start of the list,
DOT NET FRAMEWORK FOR APPLICATION DEVELOPMENT (17CS564) Page 131
Maharaja Institute of Technology Mysore Department of ISE
moving the first item up and setting its Previous property to refer to the new item, or the
AddLast method to insert an element at the end of the list, setting the Next property of the
previously last item to refer to the new item. We can also use the AddBefore and
AddAfter methods to insert an element before or after a specified item in the list.
We can find the first item in a LinkedList<T> collection by querying the First property,
whereas the Last property returns a reference to the final item in the list. To iterate
through a linked list, We can start at one end and step through the Next or Previous
references until We find an item with a null value for this property. Alternatively, We can
use a foreach statement, which iterates forward through a LinkedList<T> object and
stops automatically at the end.
We delete an item from a LinkedList<T> collection by using the Remove, RemoveFirst,
and RemoveLast methods.
DOT NET FRAMEWORK FOR APPLICATION DEVELOPMENT (17CS564) Page 132
Maharaja Institute of Technology Mysore Department of ISE
The Queue<T> collection class
The Queue<T> class implements a first-in, first-out mechanism. An element is inserted
into the queue at the back (the Enqueue operation) and is removed from the queue at the
front (the Dequeue operation).
The Stack<T> collection class
The Stack<T> class implements a last-in, first-out mechanism. An element joins the
stack at the top (the push operation) and leaves the stack at the top (the pop operation).
To visualize this, think of a stack of dishes: new dishes are added to the top and dishes
are removed from the top, making the last dish to be placed on the stack the first one to
be removed.
DOT NET FRAMEWORK FOR APPLICATION DEVELOPMENT (17CS564) Page 133
Maharaja Institute of Technology Mysore Department of ISE
The Dictionary<TKey, TValue> collection class
The array and List<T> types provide a way to map an integer index to an element,
However, sometimes We might want to implement a mapping in which the type from
which We map is not an int but rather some other type, such as string, double, or Time. In
other languages, this is often called an associative array.
The Dictionary<TKey, TValue> class implements this functionality by internally
maintaining two arrays, one for the keys from which you’re mapping and one for the
values to which you’re mapping. When We insert a key/value pair into a
Dictionary<TKey, TValue> collection, it automatically tracks which key belongs to
which value and makes it possible for Weto retrieve the value that is associated with a
specified key quickly and easily.
The design of the Dictionary<TKey, TValue> class has some important consequences:
1) A Dictionary<TKey, TValue> collection cannot contain duplicate keys.
DOT NET FRAMEWORK FOR APPLICATION DEVELOPMENT (17CS564) Page 134
Maharaja Institute of Technology Mysore Department of ISE
2) Internally, a Dictionary<TKey, TValue> collection is a sparse data structure that
operates most efficiently when it has plenty of memory with which to work.
When We use a foreach statement to iterate through a Dictionary<TKey, TValue>
collection, We get back a KeyValuePair<TKey, TValue> item. This is a structure that
contains a copy of the key and value elements of an item in the Dictionary<TKey,
TValue> collection, and We can access each element through the Key property and the
Value properties.
The SortedList<TKey, TValue> collection class
The SortedList<TKey, TValue> class is very similar to the Dictionary<TKey, TValue>
class in that you can use it to associate keys with values. The main difference is that the
keys array is always sorted, data retrieval is often quicker (or at least as quick), and
SortedList<TKey, TValue> class uses less memory.
When you insert a key/value pair into a SortedList<TKey, TValue> collection, the key is
inserted into the keys array at the correct index to keep the keys array sorted. The value is
then inserted into the values array at the same index. The SortedList<TKey, TValue>
class automatically ensures that keys and values maintain synchronization, even when
you add and remove elements. This means that you can insert key/value pairs into a
SortedList<TKey, TValue> in any sequence; they are always sorted based on the value of
the keys.
Like the Dictionary<TKey, TValue> class, a SortedList<TKey, TValue> collection
cannot contain duplicate keys. When you use a foreach statement to iterate through a
SortedList<TKey, TValue>, you receive back a KeyValuePair<TKey, TValue> item.
DOT NET FRAMEWORK FOR APPLICATION DEVELOPMENT (17CS564) Page 135
Maharaja Institute of Technology Mysore Department of ISE
However, the KeyValuePair<TKey, TValue> items will be returned sorted by the Key
property.
The HashSet<T> collection class
The HashSet<T> class is optimized for performing set operations such as
determining set membership and generating the union and intersect of sets.
We insert items into a HashSet<T> collection by using the Add method, and you
delete items by using the Remove method. However, the real power of the
HashSet<T> class is provided by the IntersectWith, UnionWith, and ExceptWith
methods. These methods modify a HashSet<T> collection to generate a new set that
either intersects with, has a union with, or does not contain the items in a specified
HashSet<T> collection.
We can also determine whether the data in one HashSet<T> collection is a superset
or subset of another by using the IsSubsetOf, IsSupersetOf, IsProperSubsetOf, and
IsProperSupersetOf methods. These methods return a Boolean value and are
nondestructive.
Internally, a HashSet<T> collection is held as a hash table, enabling fast lookup of
items. However, a large HashSet<T> collection can require a significant amount of
memory to operate quickly.
DOT NET FRAMEWORK FOR APPLICATION DEVELOPMENT (17CS564) Page 136
Maharaja Institute of Technology Mysore Department of ISE
Using collection initializers
The examples in the preceding subsections have shown you how to add individual
elements to a collection by using the method most appropriate to that collection (Add for
a List<T> collection, Enqueue for a Queue<T> collection, Push for a Stack<T>
collection, and so on). You can also initialize some collection types when you declare
them, using a syntax similar to that supported by arrays.
List<int> numbers = new List<int>(){10, 9, 8, 7, 7, 6, 5, 10, 4, 3, 2, 1};
For more complex collections that take key/value pairs, such as the Dictionary<TKey,
TValue> class, you can specify each key/value pair as an anonymous type in the
initializer list, like this:
Dictionary<string, int> ages = new Dictionary<string, int>(){{"John", 44},
{"Diana", 45}, {"James", 17}, {"Francesca", 15}};
DOT NET FRAMEWORK FOR APPLICATION DEVELOPMENT (17CS564) Page 137
Maharaja Institute of Technology Mysore Department of ISE
The Find methods, predicates, and lambda expressions
Using the dictionary-oriented collections (Dictionary<TKey, TValue>,
SortedDictionary<TKey, TValue>, and SortedList<TKey, TValue>), you can quickly
find a value by specifying the key to search for, and you can use array notation to access
the value, as you have seen in earlier examples. Other collections that support nonkeyed
random access, such as the List<T> and LinkedList<T> classes, do not support array
notation but instead provide the Find method to locate an item.
In the case of the Find method, as soon as the first match is found, the corresponding
item is returned. Note that the List<T> and LinkedList<T> classes also support other
methods such as FindLast, which returns the last matching object, and the List<T> class
additionally provides the FindAll method, which returns a List<T> collection of all
matching objects.
The easiest way to specify the predicate is to use a lambda expression. A lambda
expression is an expression that returns a method.
A lambda expression contains two of these elements: a list of parameters and a method
body. Lambda expressions do not define a method name, and the return type (if any) is
inferred from the context in which the lambda expression is used.
In the call to the Find method, the argument (Person p) => { return [Link] == 3; } is a
lambda expression that actually does the work. It has the following syntactic items:
1) A list of parameters enclosed in parentheses. As with a regular method, if the
method you are defining (as in the preceding example) takes no parameters, you
must still provide the parentheses.
2) The => operator, which indicates to the C# compiler that this is a lambda
expression.
DOT NET FRAMEWORK FOR APPLICATION DEVELOPMENT (17CS564) Page 138
Maharaja Institute of Technology Mysore Department of ISE
3) The body of the method. The example shown here is very simple, containing a
single statement that returns a Boolean value indicating whether the item specified in
the parameter matches the search criteria.
A simplified form of the Find statement shown previously looks like this
Person match = [Link](p => [Link] == 3);
Comparing arrays and collections
Here’s a summary of the important differences between arrays and collections:
An array instance has a fixed size and cannot grow or shrink. A collection can
dynamically resize itself as required.
An array can have more than one dimension. A collection is linear. However, the items in
a collection can be collections themselves, so you can imitate a multidimensional array as
a collection of collections.
We store and retrieve an item in an array by using an index. Not all collections support
this notion. For example, to store an item in a List<T> collection, you use the Add or
Insert methods, and to retrieve an item, you use the Find method.
Many of the collection classes provide a ToArray method that creates and populates an
array containing the items in the collection. The items are copied to the array and are not
removed from the collection. Additionally, these collections provide constructors that can
populate a collection directly from an array.
DOT NET FRAMEWORK FOR APPLICATION DEVELOPMENT (17CS564) Page 139