Generics
Classes are templates that you can use to build new objects. Generics are templates that you can use to build new classes. This code template can be applied to use the same code repeatedly. A generic class is tied to one or more specific data types.
Generics permit classes, interfaces, structs, delegates and methods to be parameterized by the types of data they store and manipulate. They are Type-independent algorithms and collections. They enable programmers to achieve a high level of code reuse and enhanced performance for collection classes. Generics are defined with left and right brackets: . Here ‘T’ in is the name of the type that is going to be used.
Let me demonstrate the use of Generics by taking a simple example:
namespace GenericsDemo
{
class TestGenerics
{
static void Main(string[] args)
{
GenericsExample();
}
private void GenericsExample()
{
ClsGeneric intGeneric = new ClsGeneric(1000);
ClsGeneric strGeneric = new ClsGeneric("Voila");
ClsGeneric fGeneric = new ClsGeneric(23.38f);
ClsGeneric dGeneric = new ClsGeneric(444.4);
ClsGeneric bGeneric = new ClsGeneric(true);
}
}
public class ClsGeneric
{
public ClsGeneric(T Type)
{
Console.WriteLine("T over here is {0} ", typeof(T).ToString());
}
}
}
By using a generic type parameter T, you can write a single class that other client code can use without incurring the cost or risk of runtime casts or boxing operations.
Iterators
In the earlier versions of C#, in order to use the for-each loop, you need to implement the IEnumerable interface which in turn demands the implementation of the GetEnumerator() method. This method returns an IEnumerator object using which you can use the foreach loop.
An iterator on the other hand, enables you to support foreach iteration in a class or struct without having to implement the entire IEnumerable interface.
Let me demonstrate this with an example :
public class StringCollection : System.Collections.IEnumerable
{
string[] names = { "Tom", "Dick", "Harry”, "Ralph", "Jack", "Jill” };
public System.Collections.IEnumerator GetEnumerator()
{
for (int i = 0; i < names.Length; i++)
{
yield return names[i];
// In C#1.x you would have specified it using
// return names[i].GetEnumerator();
}
}
}
class TestStringCollection
{
static void Main()
{
// Create an instance
StringCollection strNames = new StringCollection();
foreach (string name in strNames)
{
System.Console.WriteLine(name);
}
Console.WriteLine("Press any key to continue...");
Console.ReadKey();
}
}
Note: The new iterator pattern in C# 2.0 is significantly faster than its C# 1.x counterparts.
Partial Classes
C# 2.0 introduces the concept of partial type definitions for classes, structs, and interfaces that allows you to define a C# type across multiple *.cs files. This leads to easier maintenance and development as more than one developer can simultaneously write the code for the class.
public partial class A
{
public void methodA()
{
// Code for methodA
}
}
public partial class A
{
public void methodB()
{
// Code for methodB
}
}
Let me demonstrate the use of Generics by taking a simple example:
namespace GenericsDemo
{
class TestGenerics
{
static void Main(string[] args)
{
GenericsExample();
}
private void GenericsExample()
{
ClsGeneric
ClsGeneric
ClsGeneric
ClsGeneric
ClsGeneric
}
}
public class ClsGeneric
{
public ClsGeneric(T Type)
{
Console.WriteLine("T over here is {0} ", typeof(T).ToString());
}
}
}
By using a generic type parameter T, you can write a single class that other client code can use without incurring the cost or risk of runtime casts or boxing operations.
Iterators
In the earlier versions of C#, in order to use the for-each loop, you need to implement the IEnumerable interface which in turn demands the implementation of the GetEnumerator() method. This method returns an IEnumerator object using which you can use the foreach loop.
An iterator on the other hand, enables you to support foreach iteration in a class or struct without having to implement the entire IEnumerable interface.
Let me demonstrate this with an example :
public class StringCollection : System.Collections.IEnumerable
{
string[] names = { "Tom", "Dick", "Harry”, "Ralph", "Jack", "Jill” };
public System.Collections.IEnumerator GetEnumerator()
{
for (int i = 0; i < names.Length; i++)
{
yield return names[i];
// In C#1.x you would have specified it using
// return names[i].GetEnumerator();
}
}
}
class TestStringCollection
{
static void Main()
{
// Create an instance
StringCollection strNames = new StringCollection();
foreach (string name in strNames)
{
System.Console.WriteLine(name);
}
Console.WriteLine("Press any key to continue...");
Console.ReadKey();
}
}
Note: The new iterator pattern in C# 2.0 is significantly faster than its C# 1.x counterparts.
Partial Classes
C# 2.0 introduces the concept of partial type definitions for classes, structs, and interfaces that allows you to define a C# type across multiple *.cs files. This leads to easier maintenance and development as more than one developer can simultaneously write the code for the class.
public partial class A
{
public void methodA()
{
// Code for methodA
}
}
public partial class A
{
public void methodB()
{
// Code for methodB
}
}
Nullable Types
A type is said to be nullable if it can be assigned a value or can be assigned a null reference. This change makes it easier to integrate with databases, especially for fields that are optional.
A null type is very similar to its equivalent non-nullable type. The difference lies in how it is declared. For example : To declare an integer, you would declare it like this :
int iVal = 1;
However in order to enable iVal to store a null value, you will declare it like this :
int? iVal = 1;
Now the way to define any valuetype as a nullable type is to define the generic System.Nullable
System.Nullable
However this is somewhat difficult to read and that is why C#
provides a cleaner syntax for the nullable type by adding a
question mark to the immediate right of the type name when
defining a variable. The Syntax is [Value-Type]?
So System.Nullable
Similarly you can also assign it a value null and use the HasValue and Value read-only properties to test for null and retrieve the value. Here’s an example :
int? iVal = null;
if (iVal.HasValue == true)
{
System.Console.WriteLine("iVal = " + iVal.Value);
}
else
{
System.Console.WriteLine("iVal = Null");
}
Anonymous Methods
Anonymous methods help you reduce the coding overhead in instantiating delegates, by eliminating the need to create a separate method. This construct allows you to directly associate a block of code statements to a given event. In general, you can use anonymous methods anywhere you can use a delegate. An anonymous method consists of the keyword delegate, an optional parameter list, and a statement list enclosed in { and } delimiters. In some cases, the anonymous methods can omit the parameter list.
public class frmOld : Form
{
// This method connects the event handler.
public frmOld()
{
InitializeComponent();
btnOld.Click += new EventHandler(ButtonClick);
}
// This is the event handling method.
private void ButtonClick(object sender, EventArgs e)
{
MessageBox.Show("Old method of handling ButtonClick");
}
}
Using Anonymous methods :
public class frmNew : Form
{
public frmNew()
{
InitializeComponent();
btnNew.Click += delegate(object sender, EventArgs e)
{
MessageBox.Show(“New way of handling button click using anonymous methods");
};
}
}
// Declare a delegate
delegate void PrintHello(string str);
class TestAnonymous
{
static void Main()
{
hello = new PrintHello(TestAnonymous.TestMethod);
Without anonymous method you would associate the delegate with a named method. For eg:
hello = new PrintHello(TestAnonymous.TestMethod);
//and declared a method to associate it with the named delegate
static void TestMethod(string test)
{
System.Console.WriteLine(test);
}
Static Classes
Static classes can be used when there is no data or behavior in the class that depends on object identity. One of the advantages of static classes is improved performance. Since there is no instantiation of object, the framework maintains a single object in memory for static members. They are loaded automatically by the .NET Framework common language runtime (CLR) when the program or namespace containing the class is loaded.
Creating a static class is 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. Lets see an example of a static class.
Property Accessor Accessibility
Properties provide flexibility to read and write the values of private fields. They can be used as though they are public data members, but they are actually special methods called accessors. This enables data to be accessed easily while still providing the safety and flexibility of methods.
In C# 1.x, we declare a property in our class in the following manner:
class ClsProp
{
private string strMyName = "Suprotim";
public string StrMyName
{
get { return strMyName; }
set { strMyName = value; }
}
}
Here the ‘get’ and the ‘set’ accessors have the same accessibility level i.e., they are both ‘public’ as the property itself is ‘public’. However, it is sometimes useful to restrict access to one of these accessors, which was not possible for us in C# 1.x.
In C# 2.0, this has changed :
public string StrMyName
{
get
{
return strMyName;
}
protected set
{
strMyName = value;
}
}
In this example, a property called StrMyName defines a get and set accessor. The get accessor receives the accessibility level of the property itself, public in this case, while the set accessor is explicitly restricted by applying the protected access modifier to the accessor itself.
Note : This feature of having Accessor Accessibility was provided in C# v1.0 beta. However Microsoft removed it before the C# v1.0 RTM as they felt it was too complicated for developers. In C# 2.0, it has been re-introduced.
Covariance and Contravariance
In .NET 1.1, delegates could only be created with methods whose arguments strictly matched the signature of that delegate. With .NET 2.0, Covariance and contravariance provide a degree of flexibility when matching delegate methods with delegate signatures.
Conclusion
In this article, we explored the various new features introduced in C# 2.0. I hope the information was useful and I thank you for viewing it.
Writing Efficient String Functions in C#
The .NET Framework provides a set of powerful string functions. These building blocks can be used to write more complex algorithms for handling string data. However developers aiming to write fast and efficient string functions must be careful of how they use those building blocks.
To write efficient string handling functions, it is important to understand the characteristics of string objects in C#.
String Characteristics
First and foremost it is important to know that strings in .NET are class objects. There is no difference between the types System.String and string, they are both class objects. Unlike value types, class objects are stored in the heap (instead of the stack). This is an important fact because it means that creating a string object can trigger garbage collection, which is costly in terms of performance. In terms of string functions, this means we want to avoid creating new strings as much as possible.
However that is easier said than done. Another important thing about strings in .NET is that they are immutable. This means string objects cannot be modified. To edit a string object, you have to instead create a new string that will have the modification.
Working with Characters
The solution is to work with characters instead of strings as much as possible. The char object in C# is a value type, which means all char variables are stored in the stack. Furthermore, since a string is a collection of characters, converting between chars and strings is very simple.
To convert a string to a char array, use the ToCharArray() .NET function:
string myStr = “hello world”;
char[] myStrChars = myStr.ToCharArray();
To convert a char array back to a string, simply create a new instance of a string:
char[] myChars = { ‘h’, ‘e’, ‘l’, ‘l’, ‘o’, ‘ ‘, ‘w’, ‘o’, ‘r’, ‘l’, ‘d’ };
string myStr = new string(myChars);
Writing efficient string functions thus boils down to working with char arrays. However you might remember that arrays are stored in the heap. Thus there isn’t much difference between working with a string and a character array in terms of performance if we end up handling arrays in the same way as strings.
Yet this does not mean working with array is not faster. For one thing, we can make use of dynamic arrays such as List (or ArrayList in .NET Framework 1.1) to make our array management as efficient as possible.
Example Function
Let's write a very simple string function and compare the difference between using strings and char arrays. The function will capitalize all the vowels in a string (working with the English alphabet), and make all other characters lowercase.
Using just strings:
public string CapitalizeVowels(string input)
{
if (string.IsNullOrEmpty(input)) //since a string is a class object, it could be null
return string.Empty;
else
{
string output = string.Empty;
for (int i = 0; i < input.Length; i++)
{
if (input[i] == 'a' || input[i] == 'e' ||
input[i] == 'i' || input[i] == 'o' ||
input[i] == 'u')
output += input[i].ToString().ToUpper(); //Vowel
else
output += input[i].ToString().ToLower(); //Not vowel
}
return output;
}
}
Using character arrays:
public string CapitalizeVowels(string input)
{
if (string.IsNullOrEmpty(input)) //since a string is a class object, it could be null
return string.Empty;
else
{
char[] charArray = input.ToCharArray();
for (int i = 0; i < charArray.Length; i++)
{
if (charArray[i] == 'a' || charArray[i] == 'e' ||
charArray[i] == 'i' || charArray[i] == 'o' ||
charArray[i] == 'u')
charArray[i] = char.ToUpper(charArray[i]); //Vowel
else
charArray[i] = char.ToLower(charArray[i]); //Not vowel
}
return new string(charArray);
}
}
Both functions will produce the exact same results given the same input data. We can perform some basic benchmarks to compare the performance of each function. For example, the string-based function took an average of 2181ms to process the string “hello world” 1,000,000 times while the array-based function only took 448ms (measured on my computer).
Conclusion
As with anything, working with character arrays to write efficient string functions in C# must be done with care. The code can quickly become less readable. When working with more complex string algorithms, the code can become very difficult to maintain. However since the transition between working with strings and working with character arrays is easy, a combination of both can reach an advantageous middle ground.
No comments:
Post a Comment