Features of C# 2.0
- Generics
- Iterator pattern(the Yield Keyword)
- Anonymous Method,
- Nullable Types,
- Null coalescing operator(??)
- Partial classes
- Static Classes
Generics :
Generics introduce to the .NET
Framework the concept of type parameters, which make it possible to design
classes and methods that defer the specification of one or more types until the
class or method is declared and instantiated by client code.
Generics Overview
- Use generic types to maximize code reuse, type safety, and performance.
- The most common use of generics is to create collection classes.
- The .NET Framework class library contains several new generic collection classes in the System.Collections.Generic namespace. These should be used whenever possible in place of classes such as ArrayList in the System.Collections namespace.
- You can create your own generic interfaces, classes, methods, events and delegates.
- Generic classes may be constrained to enable access to methods on particular data types.
- Information on the types used in a generic data type may be obtained at run-time by means of reflection.
Generic
Type Parameter:
In a generic type or method definition, a type parameters is a
placeholder for a specific type that a client specifies when they instantiate a
variable of the generic type.
Example:
GenericList<float> list1 = new GenericList<float>();
GenericList<ExampleClass> list2 = new GenericList<ExampleClass>();
GenericList<ExampleStruct> list3 = new GenericList<ExampleStruct>();
Generic Collections:
A generic collection is strongly typed (type
safe), meaning that you can only put one type of object into it. This
eliminates type mismatches at runtime. Another benefit of type safety is that
performance is better with value type objects because they don't incur overhead
of being converted to and from type object
Generic
Classes:
Generic classes encapsulate operations that are not specific to a
particular data type. The most common use for generic classes is with collections like
linked lists, hash tables, stacks, queues, trees and so on where operations
such as adding and removing items from the collection are performed in much the
same way regardless of the type of data being stored.
When creating your own generic
classes, important considerations include the following:
- Which types to generalize into type parameters?
- As a rule, the more types you can parameterize, the more flexible and reusable your code becomes. However, too much generalization can create code that is difficult for other developers to read or understand.
- What constraints, if any, to apply to the type parameters.
- A good rule is to apply the maximum constraints possible that will still let you handle the types you must handle. For example, if you know that your generic class is intended for use only with reference types, apply the class constraint. That will prevent unintended use of your class with value types, and will enable you to use the as operator on T, and check for null values.
- Whether to factor generic behavior into base classes and subclasses.
- Because generic classes can serve as base classes, the same design considerations apply here as with non-generic classes. See the rules about inheriting from generic base classes later in this topic.
- Whether to implement one or more generic interfaces.
Example:
class BaseNode { }
class
BaseNodeGeneric<T> { }
// concrete type
class
NodeConcrete<T> : BaseNode { }
//closed constructed type
class
NodeClosed<T> : BaseNodeGeneric<int> { }
//open constructed type
class
NodeOpen<T> : BaseNodeGeneric<T> { }
Generic classes that inherit from open
constructed types must supply type arguments for any base class type parameters
that are not shared by the inheriting class, as demonstrated in the following
code:
//No error
class Node4<T> :
BaseNodeMultiple<T, int> { }
//No error
class Node5<T,
U> : BaseNodeMultiple<T, U> { }
//Generates an error
//class Node6<T>
: BaseNodeMultiple<T, U> {}
Generic classes that inherit from open
constructed types must specify constraints that are a superset of, or imply,
the constraints on the base type:
Class
SpecialNodeItem<T>: NodeItem<T>where T:
System.IComparable<T>, new() { }
Generic types can use multiple type parameters and constraints, as
follows:
Where U: System.IComparable<U>
Where V: new()
{
}
Generic Methods
You can create generic methods that can operate on any possible data type.To define a generic method you
specify the type parameter after the method name and before the parameters list.
public void
MyGenericMethod<T>(T x, T y)
{
Console.Writeline("Parameters type is {0}", typeof(T));
}
You can define the
type you want at the time you invoke the method.
int x, y;
MyGenericMethod<int>(x,
y);
The result will
be Parameter type is System.Int32
string x, y;
MyGenericMethod<string>(x,
y);
The result will be Parameter type is
System.String
You can also create a generic method with out parameters as follow:
You can also create a generic method with out parameters as follow:
public void
MyGenericMethod<T>()
{
T x;
Console.WriteLine("The type of x is
{0}", typeof(T));
}
Generic interface:
Generic
Interface often useful to define interfaces either for generic collection
classes, or for the generic classes that represent items in the collection.
With generic classes it is preferable to use generic interfaces, such as IComparable<T> rather than
IComparable, in order to avoid
boxing and unboxing operations on value types.
When
an interface is specified as a constraint on a type parameter, only types that
implement the interface can be used.
Example:
public interface ICounter<T>
{
}
You
should also add the members that the implementers will have to override. Here
is an example:
public interface ICounter<T>
{
int Count { get; }
T Get(int index);
}
In the
same way, you can derive a generic interface from another generic interface.
Here is an example:
public interface ICounter<T>
{
int Count { get; }
T Get(int index);
}
public interface IPersons<T> : ICounter<T>
{
void Add(T item);
}
Implementing
a Generic Interface
After creating the generic interface, when
deriving a class from it, follow the formula we reviewed for inheriting from a
generic class. Here is an example:
public interface ICounter<T>{int Count { get; }T Get(int index);}public interface IPersons<T> : ICounter<T>{void Add(T item);}public class People<T> : IPersons<T>{}
When implementing the derived class, you must
observe all rules that apply to interface derivation. That is, you must
implement all the members of the generic interface. Of course, you can also add
new members if you want. Here is an example:
public interface ICounter<T>{int Count { get; }T Get(int index);}public interface IPersons<T> : ICounter<T>{void Add(T item);}public class People<T> : IPersons<T>{private int size;private T[] persons;public People(){size = 0;persons = new T[10];}public int Count { get { return size; } }public void Add(T pers){persons[size] = pers;size++;}public T Get(int index) { return persons[index]; }}
After implementing the interface, you can
declare a variable of the class and use it as you see fit. Here is an example:
using System;public interface ICounter<T>{int Count { get; }T Get(int index);}public interface IPersons<T> : ICounter<T>{void Add(T item);}public class People<T> : IPersons<T>{private int size;private T[] persons;public People(){size = 0;persons = new T[10];}public int Count { get { return size; } }public void Add(T pers){persons[size] = pers;size++;}public T Get(int index) { return persons[index]; }}public class Employee{public long EmployeeNumber { get; set; }public string FirstName { get; set; }public string LastName { get; set; }public double HourlySalary { get; set; }public Employee(long number = 0, string fName = "John",string lName = "Doe", double salary = 12.05D){EmployeeNumber = number;FirstName = fName;LastName = lName;HourlySalary = salary;}public override string ToString(){base.ToString();return string.Format("================================\n" +"Employee Record\n" +"--------------------------------\n" +"Employee #: {0}\nFirst Name: {1}\n" +"Last Name: {2}\nHourly Salary: {3}",EmployeeNumber, FirstName,LastName, HourlySalary);}}public class Exercise{public static int Main(){IPersons<Employee> employees = new People<Employee>();Employee empl = null;empl = new Employee();empl.EmployeeNumber = 253055;empl.FirstName = "Joseph";empl.LastName = "Denison";empl.HourlySalary = 12.85;employees.Add(empl);empl = new Employee();empl.EmployeeNumber = 204085;empl.FirstName = "Raymond";empl.LastName = "Ramirez";empl.HourlySalary = 9.95;employees.Add(empl);empl = new Employee();empl.EmployeeNumber = 970044;empl.FirstName = "Christian";empl.LastName = "Riley";empl.HourlySalary = 14.25;employees.Add(empl);for (int i = 0; i < employees.Count; i++){Employee staff = employees.Get(i);Console.WriteLine("--------------------------------");Console.WriteLine("Employee #: {0}", staff.EmployeeNumber);Console.WriteLine("First Name: {0}", staff.FirstName);Console.WriteLine("Last Name: {0}", staff.LastName);Console.WriteLine("Hourly Salary: {0}", staff.HourlySalary);}return 0;}}
This would produce:
--------------------------------
Employee #:
253055
First Name:
Joseph
Last Name:
Denison
Hourly Salary: 12.85
--------------------------------
Employee #:
204085
First Name:
Raymond
Last Name:
Ramirez
Hourly Salary: 9.95
--------------------------------
Employee #:
970044
First Name:
Christian
Last Name:
Riley
Hourly Salary: 14.25
Press any key to continue . . .
Generic Delegates (C# Programming Guide)
A delegate can
define its own type parameters. Code that references the generic delegate can
specify the type argument to create a closed constructed type, just like when
instantiating a generic class or calling a generic method, as shown in the
following example:
Public delegate void Del<T>(T item);publicstaticvoid Notify(int i) { }Del<int> m1 = new Del<int>(Notify);Del<int> m2 = Notify;
Delegates defined within a
generic class can use the generic class type parameters in the same way that
class methods do.
class Stack<T>{T[] items;int index;publicdelegatevoid StackDelegate(T[] items);}
Code that references the delegate must specify the type argument
of the containing class, as follows:
Private static void DoWork (float[] items) { }Public static void TestStack (){Stack<float> s = new Stack<float>();Stack<float>.StackDelegate d = DoWork;}
Generic delegates are especially
useful in defining events based on the typical design pattern because the
sender argument can be strongly typed and no longer has to be cast to and from Object.
Collections:
It represents set of objects that you can access stepping
through each element in turn. In particular the set of objects can access using
FOR EACH LOOP.
-
Object
class is the base class of every type in .NET. - All the collections implement
IEnumerable
interface that is extended byICollection
interface. -
IDictionary
andIList
are also interfaces for collection which are derived fromICollection
as shown in the diagram.
What’s the difference between Lists & Dictionaries
Both lists
and dictionaries are used to store collections of data. Assume we had the
following declarations…
var dic = new Dictionary<string, long>();
var lst = new List<long>();
long data;
With a
list, you simply add the item to the list and it will add the item to the end
of the list.
lst.Add(data);
With a
dictionary, you need to specify some sort of key and the data you want to add
so that it can be uniquely identified.
dic.Add(uniquekey, data);
Because
with a dictionary you now have unique identifier, in the background they
provide all sort’s of optimized algorithms to find your associated data. What
this means is that if you are wanting to access your data it is a lot faster
than a List.
So when is it appropriate to
use either class?
if we
can guarantee that each item in my collection will have a unique identifier,
then I will use Dictionaries instead of Lists as there is a considerable
performance benefit when accessing each data item. If I cannot make this sort
of guarantee, then by default I will use a list.
Dictionary
will be sorted using key where List will
be sorted by the Object.
Iterator pattern(the Yield Keyword)
The
"yield" keyword.
Basically it is used to iterate through objects returned by a method.
Here is a simple example
that demonstrates how yield return can be used to maintain state in a method.
Every time you call GetInt() you will receive a new
incremented integer.
public static IEnumerable<int>
GetInt()
{
for (int i = 0; i < 5; i++)
yield return i;
}
{
for (int i = 0; i < 5; i++)
yield return i;
}
Here's the implementation.
class Program
{
static void Main(string[] args)
{
foreach (int i in GetInt())
Console.WriteLine("Got " + i.ToString());
}
{
static void Main(string[] args)
{
foreach (int i in GetInt())
Console.WriteLine("Got " + i.ToString());
}
public static IEnumerable<int>
GetInt()
{
for (int i = 0; i < 5; i++)
yield return i;
}
}
{
for (int i = 0; i < 5; i++)
yield return i;
}
}
Usually, the "yield" keyword will be useful when you
implement the GetEnumerator() method of the IEnumerable interface
class TestClass:
IEnumerable<int>
{
#region IEnumerable<int> Members
{
#region IEnumerable<int> Members
public IEnumerator<int> GetEnumerator()
{
for (int i = 0; i < 5; i++)
yield return i;
}
{
for (int i = 0; i < 5; i++)
yield return i;
}
#endregion
#region IEnumerable Members
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
{
return GetEnumerator();
}
}
Anonymous methods
This
is a new language feature in C# 2.0. This is a way to pass a code block as a
delegate parameter
button1.Click += delegate(System.Object o, System.EventArgs e)
{ System.Windows.Forms.MessageBox.Show("Click!"); };
By using anonymous methods, you reduce the coding overhead in
instantiating delegates by eliminating
the need to create a separate
method.
For example, specifying a
code block in the place of a delegate can be useful in a situation when having
to create a method might seem an unnecessary overhead. A good example would be
when launching a new thread. This class creates a thread and also contains the
code that the thread executes, without the need for creating an additional
method for the delegate.
Void StartThread ()
{
System.Threading.Thread t1 = new System.Threading.Thread
(Delegate ()
{
System.Console.Write ("Hello, ");
System.Console.WriteLine ("World!");
});
t1.Start ();
}
Example:
The
following example demonstrates the two ways of instantiating a delegate:
- Associating the delegate with an anonymous method.
- Associating
the delegate with a named method (
DoWork
).
In each
case, a message is displayed when the delegate is invoked.
// declare a delegate
Delegate void Printer (string s);
Class TestClass
{
Static void Main ()
{
// Instatiate the delegate type using an anonymous method:
Printer p = delegate (string j)
{
System.Console.WriteLine (j);
};
// Results from the anonymous delegate call:
p("The delegate using the anonymous method is called.");
// the delegate instantiation using a named method "DoWork":
p = new Printer (TestClass.DoWork);
// Results from the old style delegate call:
p ("The delegate using the named method is called.");
}
// the method associated with the named delegate:
Static void DoWork (string k)
{
System.Console.WriteLine (k);
}
}
Output:
The
delegate using the anonymous method is called.
The
delegate using the named method is called.
Nullable Types:
- Nullable types are instances of the System. Nullable struct.
- A Nullable type can represent the normal range of values for its underlying value type, plus an additional null value.
- Nullable types represent value-type variables that can be assigned the value of null. You cannot create a Nullable type based on a reference type. (Reference types already support the null value.)
- The syntax T? is shorthand for System. Nullable<T>, where T is a value type. The two forms are interchangeable.
Assign a value to a Nullable type in the same way as for an ordinary value type, for example
int? x = 10;
ordouble? d = 4.108;
Use the HasValue and Value read-only properties to test for null and retrieve the value, for example
if(x.HasValue) j = x.Value;
- The HasValue property returns true if the variable contains a value, or false if it is null.
- The Value property returns a value if one is assigned, otherwise a System.InvalidOperationException is thrown.
- The default value for a Nullable type variable sets HasValue to false. The Value is undefined.
Use the?? Operator to assign a default value that will be applied when a Nullable type whose current value is null is assigned to a non-Nullable type, for example
int? x = null; int y = x ?? -1;
- Nested
Nullable types are not allowed. The following line will not compile:
Nullable<Nullable<int>> n;
- For example, a Nullable<Int32>, pronounced "Nullable of Int32," can be assigned any value from -2147483648 to 2147483647, or it can be assigned the null value. A Nullable<bool> can be assigned the values true or false, or null.
Example:
Class NullableExample
{
Static void Main ()
{
int? num = null;
If (num.HasValue == true)
{
System.Console.WriteLine ("num = " + num.Value);
}
else
{
System.Console.WriteLine ("num = Null");
}
//y is set to zero
Int y = num.GetValueOrDefault ();
// num.Value throws an InvalidOperationException if num.HasValue is false
Try
{
y = num.Value;
}
Catch (System.InvalidOperationException e)
{
System.Console.WriteLine (e.Message);
}
}
}
The above
will display the output:
num =
Null
Nullable
object must have a value.
Null
coalescing operator (??):
The ?? Operator is called the null-coalescing operator and is
used to define a default value for a nullable value types as well as reference
types. It returns the left-hand operand if it is not null; otherwise it returns
the right operand.
Example:
int b = a ?? -1;
it means if the value of a is
null then b=a otherwise the value of b will be -1.
Simply this means
If (a == null)
b = a;
Else
b = -1;
b = a;
Else
b = -1;
//The??
Operator also works with reference types:
//message
= param, unless param is null
//in
which case message = "No message"
String message
= param?? "No message";
To use the null-coalescing-operator, there are
some compile-time ground rules.
- The left-hand-side must evaluate to a reference or nullable type.
- All evaluated statements must be of matching type, unless they can be implicitly converted.
Partial classes
A partial
class is a class whose definition is present in 2 or more
files. Each source file contains a section of the class, and all parts are combined
when the application is compiled.
To split a class definition, use the partial keyword as shown in the example
below. Student class is split into 2 parts. The first part defines the study()
method and the second part defines the Play() method. When we compile this
program both the parts will be combined and compiled. Note that both the parts
uses partial keyword and public access modifier.
EXAMPLE
using System;
namespace PartialClass
{
public partial class Student
{
public void Study()
{
Console.WriteLine("I am studying");
}
}
public partial class Student
{
public void Play()
{
Console.WriteLine("I am Playing");
}
}
public class Demo
{
public static void Main()
{
Student StudentObject = new Student();
StudentObject.Study();
StudentObject. Play ();
}
}
}
The following points in mind
when creating partial classes.
- All the parts must use the partial keyword.
- All the parts must be available at compile time to form the final class.
- All the parts must have the same access modifiers - public, private, protected etc.
- Any class members declared in a partial definition are available to all the other parts.
- The final class is the combination of all the parts at compile time.
Advantages
of using Partial Classes
- When working on large projects, spreading a class over separate files enables multiple programmers to work on it at the same time.
- When working with automatically generated source, code can be added to the class without having to recreate the source file. Visual Studio uses this approach when it creates Windows Forms, Web service wrapper code, and so on. You can create code that uses these classes without having to modify the file created by Visual Studio.
Static Classes:
A class can be declared static, indicating that it contains only static members.
It is not possible to create instances of a static class using the new keyword.
Static classes are loaded automatically by the .NET Framework common language runtime (CLR)
when the program or namespace containing the class is loaded.
The main features of a static
class are:
- They only contain static members.
- They cannot be instantiated.
- They are sealed.
- They cannot contain Instance Constructors
- Creating a static class is therefore much the same as creating a class that contains only static members and a private constructor. A private constructor prevents the class from being instantiated.
- The advantage of using a static class is that the compiler can check to make sure that no instance members are accidentally added. The compiler will guarantee that instances of this class cannot be created.
- Static classes are sealed and therefore cannot be inherited. Static classes cannot contain a constructor, although it is still possible to declare a static constructor to assign initial values or set up some static state.
Static
Methods:
When a method
declaration includes a
static
modifier, that method is said to
be a static method.
A static method
does not operate on a specific instance, and it is a compile-time error to
refer to
this
in a static method.
When a method is
referenced in a member-access of the form
E.M
, if M
is a static method, E
must denote a type containing M,
If M
is an instance method E
must denote an instance of a type containing M
.
Instance
Methods:
When no
static
modifier is
present, the method is said to be an instance method.
An instance
method operates on a given instance of a class, and that instance can be
accessed as
this
No comments :
Post a Comment