Wednesday, July 1, 2015

C# in Nutshell Chapter 4 Advanced C#

Delegates

A delegate is an object that knows how to call a method.

delegate int Transformer (int x);
class Test
{
static void Main()
{
Transformer t = Square;  // Create delegate instance
int result = t(3);    // Invoke delegate
Console.WriteLine (result);  // 9
}
static int Square (int x) { return x * x; }
}

plug-in methods with Delegates 

public delegate int Transformer (int x);
class Util
{
public static void Transform (int[] values, Transformer t)
{
for (int i = 0; i < values.Length; i++)
values[i] = t (values[i]);
}
}
class Test
{
static void Main()
{
int[] values = { 1, 2, 3 };
Util.Transform (values, Square);  // Hook in the Square method
foreach (int i in values)
Console.Write (i + " ");   // 1 4 9
}

static int Square (int x) { return x * x; }
}

Multicast Delegates

All delegate instances have multicast capability. This means that a delegate instance
can reference not just a single target method, but also a list of target methods. The
+ and += operators combine delegate instances. For example:

SomeDelegate d = SomeMethod1;
d += SomeMethod2;

Note. Delegates are immutable, so when you call += or −=, you’re in fact creating a new delegate instance and assigning it to the existing variable.

If a multicast delegate has a nonvoid return type, the caller receives the return value from the last method to be invoked. The preceding methods are still called, but their return values are discarded.

Multicast example:

class Test
{
static void Main()
{
ProgressReporter p = WriteProgressToConsole;
p += WriteProgressToFile;
Util.HardWork (p);
}
static void WriteProgressToConsole (int percentComplete)
{
Console.WriteLine (percentComplete);
}
static void WriteProgressToFile (int percentComplete)
{
System.IO.File.WriteAllText ("progress.txt",
percentComplete.ToString());
}
}


Delegate v.s Interface

A problem that can be solved with a delegate can also be solved with an interface.

A delegate design may be a better choice than an interface design if one or more of
these conditions are true:
• The interface defines only a single method.
• Multicast capability is needed.
• The subscriber needs to implement the interface multiple times.


Delegate Parameter compatibility

When you call a method, you can supply arguments that have more specific types
than the parameters of that method. This is ordinary polymorphic behavior. For
exactly the same reason, a delegate can have more specific parameter types than its
method target. This is called contravariance.

Delegate Return type compatibility

If you call a method, you may get back a type that is more specific than what you
asked for. This is ordinary polymorphic behavior. For exactly the same reason, a
delegate target method may return a more specific type than described by the delegate.
This is called covariance.

Events in Delegates

The main purpose of events is to prevent subscribers from interfering with each other.
For example below, if we remove the event keyword from our example so that PriceChanged becomes an ordinary delegate field, our example would give the same results. However, Stock
would be less robust, in that subscribers could do the following things to interfere
with each other:
  1. Replace other subscribers by reassigning PriceChanged (instead of using the += operator).
  2. Clear all subscribers (by setting PriceChanged to null).
  3. Broadcast to other subscribers by invoking the delegate.
Code within the Stock type has full access to PriceChanged and can treat it as a delegate. Code outside of Stock can only perform += and −= operations on the PriceChanged event.


public delegate void PriceChangedHandler (decimal oldPrice, decimal newPrice);

public class Stock
{
string symbol;
decimal price;
public Stock (string symbol) { this.symbol = symbol; }
public event PriceChangedHandler PriceChanged;
public decimal Price
{
get { return price; }
set
{
if (price == value) return; // Exit if nothing has changed
decimal oldPrice = price;
price = value;
if (PriceChanged != null) // If invocation list not
PriceChanged (oldPrice, price); // empty, fire event.
}
}
}

throw v.s. throw ex

try { ... }
catch (Exception ex)
{
// Log error
...
throw; // Rethrow same exception
}

If we replaced throw with throw ex, the example would still work, but the StackTrace property of the newly propagated exception would no longer reflect the original error.


Preprocessor Directives



































No comments:

Post a Comment