Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Лаба №1 / books / csharp_ebook.pdf
Скачиваний:
77
Добавлен:
03.03.2016
Размер:
3.69 Mб
Скачать

Programmers Heaven: C# School

Author's Note: It occurs to me, and perhaps to you too, that while partial classes seem to do a good job of splitting up functionality, they do little to help with re-use. Object orientation achieves both – by splitting functionality between classes, we gain re-usability since a class can then be use elsewhere. You might be able to imagine something like a partial class, but that stands alone from any particular class and can be “merged into” other classes as needed. This isn’t a new idea – it has been done in other languages already under names such as roles and mixins. I don’t believe it is on the horizon in C#, though.

Nullable types

When dealing with reference types it is possible to set the reference to null, indicating that there is no object being referenced. This can be used to indicate that, for example, no useful data was available or accessible. You can imagine this situation when fetching data from a database – if the query fails to find a row, a null reference can be returned to indicate this; otherwise, a reference to object corresponding to the row that was found can be returned.

Now imagine a situation where we’re doing a query but just want one value back, and so our database fetch method will just return, for example, an integer. A problem arises here, for with value types there is no null value. One solution, if it is an integer that is being fetched, is to make a value of zero mean “nothing was found”. This is not always possible, however – sometimes any integer is valid and we need another way to signal that there is no value.

Nullable types address this problem by allowing value types to take a null value. To specify that a value type should be nullable, simply place a question mark after the name of the type:

int? x = null;

double? y = 5.2;

Notice that now as well as being able to assign a value, we can also assign null. To test whether a nullable variable has been given a value or if it is null, either test it for equality to null as you would with a reference or test its boolean HasValue property, which will be set to false if the variable is null.

if (x != null)

// Alternatively, if (x.HasValue)

{

 

// It's not null.

}

else

{

// It is null.

}

You can perform arithmetic and logical operations on nullable value types just as you would on their non-nullable variants. This behaves exactly the same when the variables are not null.

int? x = 6;

332

Programmers Heaven: C# School

int? y = 7;

int? z = x * y; // z is now 42

However, if a null variable gets evaluated while evaluating some arithmetic or logical expression, the result of that whole expression becomes null.

int? x = 6;

int? y = null;

int? z = x * y; // z is now null

Finally, there is some extra syntax for assigning a default value if a nullable variable is null. This is the new null coalescing operator, which is spelt “??”. It is useful when assigning a nullable type to a non-nullable type.

int? x = 5; int? y = null;

int a = x ?? 0; // a is 5 int b = y ?? 0; // b is 0

Directly assigning a nullable variable to a variable of the equivalent non-nullable type is a compile time error.

int? x = 5;

int a = x; // Ooh, naughty.

You can insert a cast, which stops the compiler complaining:

int? x = 5;

int a = (int) x; // OK

However, if x was null:

int? x = null;

int a = (int) x;

Then it will compile, but an exception will be thrown at runtime.

System.InvalidOperationException was unhandled

Message="Nullable object must have a value."

Essentially casts convert nulls to exceptions, which may be useful from time to time, though you might be able to throw a more helpful exception explicitly.

333

Programmers Heaven: C# School

The question mark syntax and null coalescing operator are really just syntactic sugar. Under the hood this compiles down to using the Nullable type, which is a generic value type in the standard .Net class library. Therefore other

.Net languages without special support for nullable types can still work with them. Here is a short example of using the Nullable type directly, with the prettier C# commented in to the right.

Nullable<int> x = null;

// Same as int? x = null;

int a = x.HasValue ? x.Value : 0;

// Same as int a = x ?? 0;

 

 

Nullable types are another example of how generics can be put to good use.

Anonymous methods in event handling

Delegates in C# enable us to call methods indirectly. That is, instead of knowing the name of the method that is being called, we have a reference to it and make the call through the reference. Delegates form the basis of the .Net event system, and a common usage pattern is to write some method to handle an event…

public void MyButton_OnClick(object sender, EventArgs e)

{

// Handle the event…

}

…and then add it to the multicast delegate for that event.

myButton.Click += new EventHandler(MyButton_OnClick);

Usually the method that handles the event (MyButton_OnClick in this case) is only ever called when the event occurs - that is, it is only ever called using the delegate and not directly using its name. Furthermore, many event handling methods are only a few lines of code long. Also, it would be good to be able to somehow draw the handler method and its subscription to the event closer together. Anonymous methods address all of these observations and more by enabling us to create a delegate type and supply the implementation for the method that the delegate will reference immediately afterwards. The syntax for an anonymous method is as follows.

myButton.Click += delegate(object sender, EventArgs e)

{

// Handle the event...

};

Notice that the first line looks somewhat similar to the subscription to the event handler, but instead of specifying which method to subscribe to the handler, a new delegate type is being created using the keyword “delegate”. Immediately following the creation of the delegate type is a method body, terminated by a semicolon after the closing curly bracket, which is easy to forget. The parameters for the methods will be accessible through the names specified in the delegate type definition, as demonstrated below.

334

Programmers Heaven: C# School

myButton.Click += delegate(object sender, System.EventArgs e)

{

MessageBox.Show(e.ToString());

};

Adventures with anonymous methods

So far anonymous methods may appear to be little more than a neat trick to reduce the amount of code that needs to be written for event handlers. One of the things makes them somewhat more powerful than this is that the local variables of the enclosing method are visible inside them. Consider the following program.

//Create a list containing the numbers 1 to 10. List<int> numbers = new List<int>();

for (int i = 1; i <= 10; i++) numbers.Add(i);

//Add them all together.

int sum = 0;

numbers.ForEach(delegate(int x) { sum += x; }); Console.WriteLine(sum); // Prints 55

The second line from the bottom contains the interesting use of an anonymous method. The ForEach method takes a delegate reference and for each value in the collection makes a call through that delegate. Note that the delegate reference must be of a delegate type that takes one parameter (of type int in this case). Here an anonymous method is defined by following the delegate definition with a chunk of code in curly braces. What is perhaps somewhat surprising is that we can use the variable “sum” inside the anonymous method. In the above example we increment it by the value that was passed. Essentially this is just a complicated way to write a foreach loop, but the technique is far more general and can be used in a wide variety of situations.

You might at this point be wondering what happens if you return a delegate that references an anonymous method that uses one of the local variables in its enclosing method. Surely local variables only live as long as the method that is calling them is executing, and thus the variable referred to by the anonymous method will no longer exist? In fact, this is not the case. Consider what the following program will do when you run it.

using System;

using System.Collections.Generic;

namespace CSharp2Examples

{

//A new delegate type that takes no parameters and returns

//an integer.

335

Programmers Heaven: C# School

delegate int Counter();

class Program

{

static Counter GetCounter(int start)

{

//Counter variable outside of the anonymous method. int count = start;

//Return our counter method.

return delegate()

{

count++; return count;

};

}

static void Main(string[] args)

{

//Create a counter starting at 15. Counter c = GetCounter(15);

//Loop 5 times calling the anonymous method. for (int i = 0; i < 5; i++)

{

int value = c(); Console.WriteLine(value);

}

}

}

}

The program will give the following output:

16

17

18

19

20

The local variable “count” is somehow being kept around. Under the hood the compiler is actually doing a fairly elaborate piece of analysis and transformation, creating an anonymous nested class and placing any locals that

336

Соседние файлы в папке books