Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Professional Visual Studio 2005 (2006) [eng]

.pdf
Скачиваний:
117
Добавлен:
16.08.2013
Размер:
21.9 Mб
Скачать

Chapter 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