Professional Visual Studio 2005 (2006) [eng]
.pdfChapter 22
With this in mind, one of the most well-known delegates is the EventHandler delegate, which is defined within the .NET Framework and can be used in place of defining your own event type.
With the introduction of generics, and in particular generic methods, it is no surprise that you can also have generic delegates. This makes possible the addition of a number of predefined delegates to the
.NET Framework to support common tasks such as find, comparison, and conversion operations. This section offers a brief summary of some of the predefined delegates at your disposal.
Action
The Action delegate is useful for performing a specific action on a list of items using the Array.ForEach or List.ForEach methods. The syntax is as follows:
Public Delegate Sub Action(Of T) (obj As T)
For example, the following sample gives each employee a $2,000 pay raise. Because you know that employees is a List(Of Employees), when you call the ForEach method it is expecting a delegate,
Action(Of Employee), which both PrintEmployee and IncreaseSalary match:
Private Sub BtnLanguageClick(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles BtnLanguage.Click Dim employees As List(Of Employee) = GetEmployees()
employees.ForEach(AddressOf PrintEmployee) employees.ForEach(AddressOf IncreaseSalary) employees.ForEach(AddressOf PrintEmployee)
End Sub
Private Sub PrintEmployee(ByVal emp As Employee) Console.WriteLine(“Employee: {0} - ${1}”, emp.Name, emp.Wage)
End Sub
Private Sub IncreaseSalary(ByVal emp As Employee) emp.Wage += 2000
End Sub
Comparison
The Comparison delegate accepts two parameters of the same type and returns a value indicating how those items are relative to each other (Less than 0 x before y, 0 x is the same as y; More than 0 x after y). The syntax for the Comparison delegate is as follows:
Public Delegate Function Comparison(Of T) (x As T, y As T) As Integer
This delegate is most commonly used by the Array.Sort and List.Sort methods, as shown in the following example in which employees are sorted by wage and then name:
Private Sub BtnLanguageClick(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles BtnLanguage.Click Dim employees As List(Of Employee) = GetEmployees()
employees.ForEach(AddressOf PrintEmployee)
296
Generics, Nullable Types, and Partial Types
employees.Sort(AddressOf CompareEmployees) employees.ForEach(AddressOf PrintEmployee)
End Sub
Private Function CompareEmployees(ByVal emp1 As Employee, _
ByVal emp2 As Employee) As Integer
If emp1.Wage = emp2.Wage Then Return String.Compare(emp1.Name, emp2.Name, True) If emp1.Wage > emp2.Wage Then Return -1 Else Return 1
End Function
Converter
The Converter delegate is used to convert one type to another. Its syntax is as follows:
Public Delegate Function Converter(Of TInput, TOutput) (input As TInput) As TOutput
The Converter delegate is usually applied to an entire collection using Array.ConvertAll or List.ConvertAll, as shown in the following example in which a list of formatted strings is generated from the employees list:
Private Sub BtnLanguageClick(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles BtnLanguage.Click Dim employees As List(Of Employee) = GetEmployees()
employees.ForEach(AddressOf PrintEmployee) Dim mainFrameInput As List(Of String) = _
employees.ConvertAll(Of String)(AddressOf ConvertToString) mainFrameInput.ForEach(AddressOf PrintMainFrameInput)
End Sub
Private Function ConvertToString(ByVal emp As Employee) As String
Return String.Format(“KEY-{0} AMT-{1}”, emp.Name, emp.Wage)
End Function
Private Sub PrintMainFrameInput(ByVal str As String)
Console.WriteLine(str)
End Sub
Predicate
The Predicate delegate is used to query an item to determine whether it meets a search criterion:
Public Delegate Function Predicate(Of T) (obj As T) As Boolean
Predicate is used by the List and Array types in a number of methods, particularly those such as Find and FindAll, which query the collection and return items that meet the criterion. The following example looks for all employees who are below the threshold value, as they all deserve a pay raise:
Private Sub BtnLanguageClick(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles BtnLanguage.Click Dim employees As List(Of Employee) = GetEmployees()
employees.ForEach(AddressOf PrintEmployee)
297
Chapter 22
Dim lowWageEmployees As List(Of Employee) = _
employees.FindAll(AddressOf BelowWageThreshold) lowWageEmployees.ForEach(AddressOf IncreaseSalary)
employees.ForEach(AddressOf PrintEmployee) End Sub
Private Function BelowWageThreshold(ByVal emp As Employee) As Boolean Return emp.Wage < 50000
End Function
EventHandler
The last delegate to discuss in this section is the EventHandler delegate; its syntax is as follows:
Public Delegate Sub EventHandler(Of TEventArgs As EventArgs) _
(sender As Object, e As TEventArgs)
Most developers are familiar with the non-generic version of this delegate, which expects an EventArgs object as the second parameter. In the generic form, the second parameter can be any class so long as it derives from the EventArgs class, meaning that event handlers can be strongly typed without having to declare a new delegate for each event:
Private Sub BtnLanguageClick(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles BtnLanguage.Click Dim employees As List(Of Employee) = GetEmployees()
employees.ForEach(AddressOf PrintEmployee) employees.ForEach(AddressOf SubscribeToEmployeeEvents) Dim lowWageEmployees As List(Of Employee) = _
employees.FindAll(AddressOf BelowWageThreshold) lowWageEmployees.ForEach(AddressOf IncreaseSalary)
End Sub
Private Sub SubscribeToEmployeeEvents(ByVal emp As Employee)
AddHandler emp.EmployeeChanged, AddressOf EmployeeChangedEventHandler End Sub
Private Sub EmployeeChangedEventHandler(ByVal sender As Object, _
ByVal e As Employee.EmployeeEventArgs)
Console.WriteLine(“Employee ‘{0}’ wage changed from ${1} to ${2}”, _ e.Employee.Name, e.OldWage, e.Employee.Wage)
End Sub
Partial Class Employee
Public Class EmployeeEventArgs
Inherits EventArgs
Private m_OldWage As Integer
Private m_Employee As Employee
Public Sub New(ByVal emp As Employee, ByVal oldWage As Integer)
Me.m_Employee = emp
Me.m_OldWage = oldWage
End Sub
Public ReadOnly Property Employee() As Employee
298
Generics, Nullable Types, and Partial Types
Get
Return Me.m_Employee
End Get
End Property
Public ReadOnly Property OldWage() As Integer
Get
Return Me.m_OldWage
End Get
End Property
End Class
Public Event EmployeeChanged As EventHandler(Of EmployeeEventArgs)
Public Property Wage() As Integer Get
Return Me.m_Wage End Get
Set(ByVal value As Integer)
If Not Me.m_Wage = value Then
Dim oldWage As Integer = Me.m_Wage Me.m_Wage = value
RaiseEvent EmployeeChanged(Me, New EmployeeEventArgs(Me, oldWage)) End If
End Set End Property
End Class
This example has modified the Employee class to raise an EmployeeChangedEvent when the employee’s wage is changed. Although the Employee object is passed as the sender of the event, it is also passed into the EmployeeChangedEventArgs so there is strongly typed support when handling the event. The main routine subscribes to the change event for each of the Employee objects before searching for those that need a pay raise. When those employees are given a pay raise, the EmployeeChangedEvent is raised and the event handler is used to print the list of employees who received a pay raise.
Proper ty Accessibility
Good coding practices state that fields should be private and wrapped with a property. This property should be used to access the backing field, rather than to refer to the field itself. However, one of the difficulties has been exposing a public read property so that other classes can read the value, but also making the write part of the property either private or at least protected, preventing other classes from making changes to the value of the field. The only workaround for this was to declare two properties, a public read-only property and a private, or protected, read-write, or just write-only, property. Visual Studio 2005 now lets you define properties with different levels of accessibility for the read and write components. For example, the Name property has a public read method and a protected write method:
C#
public string Name
{
get { return m_Name; }
protected set { m_Name = value; }
}
299
Chapter 22
VB.NET
Public Property Name() As String
Get
Return Me.m_Name
End Get
Protected Set(ByVal value As String)
Me.m_Name = value
End Set
End Property
The limitation on this is that the individual read or write components cannot have an accessibility that is more open than the property itself. For example, if you define the property to be protected, you cannot make the read component public. Instead, you need to make the property public and the write component protected.
Custom Events
Both C# and VB.NET can declare custom events that determine what happens when someone subscribes or unsubscribes from an event, and how the subscribers list is stored. Note that the VB.NET example is more verbose, but it enables you to control how the event is actually raised. In this case, each handler is called asynchronously for concurrent access. The RaiseEvent waits for all events to be fully raised before resuming:
C#
List<EventHandler> EventHandlerList = new List<EventHandler>();
public event EventHandler Click{
add{EventHandlerList.Add(value);}
remove{EventHandlerList.Remove(value);}
}
VB.NET
Private EventHandlerList As New ArrayList
Public Custom Event Click As EventHandler AddHandler(ByVal value As EventHandler)
EventHandlerList.Add(value) End AddHandler
RemoveHandler(ByVal value As EventHandler) EventHandlerList.Remove(value)
End RemoveHandler
RaiseEvent(ByVal sender As Object, ByVal e As EventArgs) Dim results As New List(Of IAsyncResult)
For Each handler As EventHandler In EventHandlerList If handler IsNot Nothing Then
results.Add(handler.BeginInvoke(sender, e, Nothing, Nothing)) End If
Next
While results.Find(AddressOf IsFinished) IsNot Nothing Threading.Thread.Sleep(250)
End While
300
Generics, Nullable Types, and Partial Types
End RaiseEvent
End Event
Private Function IsFinished(ByVal async As IAsyncResult) As Boolean
Return async.IsCompleted
End Function
Summar y
This chapter explained how generic types, methods, and delegates can significantly improve the efficiency with which you can write and maintain code. You were also introduced to features — such as property accessibility and custom events — that give you full control over your code and the way it executes.
The following chapter examines C# and VB.NET specifics and the support they have within Visual Studio 2005.
301
Language-Specific Features
One of the hotly debated topics among developers is which .NET language is the best for performance, efficient programming, readability, and so on. While each of the .NET languages has a different objective and target market, developers are continually seeing long-term feature parity. In fact, there are very few circumstances where it is impossible to do something in one language that can be done in another. This chapter examines some features that are specific to either C# or VB.NET. It is likely that in the next release of Visual Studio you will see these features adopted by other .NET languages.
C#
The C# language has always been at the forefront of language innovation, with a focus on writing efficient code. Version 2.0 of the language includes anonymous methods, iterators, and static classes that help tidy up your code make it more efficient.
Anonymous Methods
Anonymous methods are essentially methods that do not have a name, and at surface level they appear and behave the same way as a normal method. A common use for an anonymous method is writing event handlers. Instead of declaring a method and adding a new delegate instance to the event, this can be condensed into a single statement, with the anonymous method appearing inline. This is illustrated in the following example:
private void Form1_Load(object sender, EventArgs e)
{
this.button1.Click += new EventHandler(OldStyleEventHandler);
this.button1.Click += delegate{
Console.WriteLine(“Button pressed - new
school!”);
Chapter 23
};
}
private void OldStyleEventHandler(object sender, EventArgs e){ Console.WriteLine(“Button pressed - old school!”);
}
The true power of anonymous methods is that they can reference variables declared in the method in which the anonymous method appears. The following example searches a list of employees, as you did in the previous chapter, for all employees that have a salary less than $40,000. The difference here is that instead of defining this threshold in the predicate method, the amount is held in a method variable. This dramatically reduces the amount of code you have to write to pass variables to a predicate method. The alternative is to define a class variable and use that to pass in the value to the predicate method.
private void ButtonClick(object sender, EventArgs e)
{
List<Employee> employees = GetEmployees(); int wage = 0;
bool reverse = false;
Predicate<Employee> employeeSearch = delegate(Employee emp)
{
if (reverse==false)
return (emp.Wage < wage);
else
return !(emp.Wage < wage);
};
wage = 40000;
List<Employee> lowWageEmployees = employees.FindAll(employeeSearch); wage=60000;
List<Employee> mediumWageEmployees = employees.FindAll(employeeSearch); reverse = true;
List<Employee> highWageEmployees = employees.FindAll(employeeSearch);
}
In this example, you can see that an anonymous method has been declared within the ButtonClick method. The anonymous method references two variables from the containing method: wage and reverse. Although the anonymous method is declared early in the method, when it is evaluated it uses the current values of these variables.
Iterators
Prior to generics, you not only had to write your own custom collections, you also had to write enumerators that could be used to iterate through the collection. In addition, if you wanted to define an enumerator that iterated through the collection in a different order, you had to generate an entire class that maintained state information. Writing an iterator in C# dramatically reduces the amount of code you have to write in order to iterate through a collection, as illustrated in the following example:
public class ListIterator<T> : IEnumerable<T>
{
List<T> myList;
public ListIterator(List<T> listToIterate){
304
Language-Specific Features
myList=listToIterate;
}
public IEnumerator<T> GetEnumerator() { foreach (T x in myList) yield return x;
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
public IEnumerable<T> Top5OddItems { get {
int cnt = 0;
for (int i = 0; i < myList.Count - 1; i++){ if (i % 2 == 0){
cnt += 1;
yield return myList[i];
}
if (cnt == 5) yield break;
}
}
}
}
In this example, the keyword yield is used to return a particular value in the collection. At the end of the collection, you can either allow the method to return, as the first iterator does, or you can use yield break to indicate the end of the collection. Both the GetEnumerator and Top5OddItems iterators can be used to cycle through the items in the List, as shown in the following snippet:
public static void PrintNumbers()
{
List<int> randomNumbers = GetNumbers();
Console.WriteLine(“Normal Enumeration”);
foreach (int x in (new ListIterator<int>(randomNumbers)))
{
Console.WriteLine(“{0}”, x.ToString());
}
Console.WriteLine(“Top 5 Odd Values”);
foreach (int x in (new ListIterator<int>(randomNumbers)).Top5OddItems)
{
Console.WriteLine(“{0}”, x.ToString());
}
}
Static Classes
At some stage most of you have written a class that only contains static methods. In the past there was always the danger that someone would create an instance of this class. The only way to prevent this was to create a private constructor and make the class non-inheritable. In the future, however, an instance
305