Objective 1.3: Implement program flow
1.31 Working with Boolean expressions
*If the runtime notices that the left part of your OR
operation is true
, it doesn’t have to evaluate the right part of your expression. This is called short-circuiting. So does AND
operation.
The Exclusive OR
operator (XOR
) returns true
only when exactly one of the operands is true
.
1.32 Making decisions
The null-coalescing operator
The ??
operator is called the null-coalescing operator
. It returns the left value if it’s not null; otherwise, the right operand.
LISTING 1-59 Nesting the null-coalescing operator
int? x = null;
int? z = null;
int y = x ??
z ??
-1;
The conditional operator
The ?:
operator is called the conditional operator
. If the expression is true, the first value is returned; otherwise, the second.
1.33 Iterating across collections
The for loop
You can use multiple statements in each part of your for
loop.
LISTING 1-65 A for loop with multiple loop variables
int[] values = { 1, 2, 3, 4, 5, 6 };
for (int x = 0, y = values.Length - 1;
((x < values.Length) && (y >= 0));
x++, y--)
{
Console.Write(values[x]);
Console.Write(values[y]);
}
// Displays
// 162534435261
LISTING 1-66 A for loop with a custom increment
int[] values = { 1, 2, 3, 4, 5, 6 };
for (int index = 0; index < values.Length; index += 2)
{
Console.Write(values[index]);
}
// Displays
// 135
The foreach loop
In foreach
loop The loop
variable cannot be modified. You can make modifications to the object that the variable points to, but you can’t assign a new value to it.
LISTING 1-72 Changing items in a foreach
class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
void CannotChangeForeachIterationVariable()
{
var people = new List<Person>
{
new Person() { FirstName = "John", LastName = "Doe"},
new Person() { FirstName = "Jane", LastName = "Doe"},
};
foreach (Person p in people)
{
p.LastName = "Changed"; // This is allowed
// p = new Person(); // This gives a compile error
}
}
LISTING 1-73 The compiler-generated code for a foreach loop
List<Person>.Enumerator e = people.GetEnumerator();
try
{
Person v;
while (e.MoveNext())
{
v = e.Current;
}
}
finally
{
System.IDisposable d = e as System.IDisposable;
if (d != null) d.Dispose();
}
If you change the value of e.Current to something else, the iterator pattern can’t determine what to do when e.MoveNext
is called. This is why it’s not allowed to change the value of the iteration variable in a foreach
statement.
Jump statements
You should try to avoid break
and continue
to improve the readability of your code. As a guideline, you should try to avoid using goto
.
MORE INFO ABOUT JUMP STATEMENTS
Objective summary
- Boolean expressions can use several operators: ==, !=, <, >, <=, >=, !. Those operators can be combined together by using AND (&&), OR (||) and XOR (^).
- You can use the if-else statement to execute code depending on a specific condition.
- The switch statement can be used when matching a value against a couple of options.
- The for loop can be used when iterating over a collection where you know the number of iterations in advance.
- A while loop can be used to execute some code while a condition is true; do-while
should be used when the code should be executed at least once. - foreach can be used to iterate over collections.
- Jump statements such as break, goto, and continue can be used to transfer control to another line of the program.
Objective 1.4: Create and implement events and callbacks
An event
can be used to provide notifications.
1.41 Understanding delegates
In C#, delegates
form the basic building blocks for events. A delegate
is a type that defines a method signature.
LISTING 1-75 Using a delegate
public delegate int Calculate(int x, int y);
public int Add(int x, int y) { return x + y; }
public int Multiply(int x, int y) { return x * y; }
public void UseDelegate()
{
Calculate calc = Add;
Console.WriteLine(calc(3, 4)); // Displays 7
calc = Multiply;
Console.WriteLine(calc(3, 4)); // Displays 12
}
Delegates
can be nested in other types and they can then be used as
a nested type.
An instantiated delegate
is an object; you can pass it around and give it as an argument to other methods.
You can combine delegates
together. This is called multi-casting.
LISTING 1-76 A multicast delegate
public void MethodOne()
{
Console.WriteLine("MethodOne");
}
public void MethodTwo()
{
Console.WriteLine("MethodTwo");
}
public delegate void Del();
public void Multicast()
{
Del d = MethodOne;
d += MethodTwo;
d();
}
// Displays
// MethodOne
// MethodTwo
All this is possible because delegates inherit from the System.MulticastDelegate
class that in turn inherits from System.Delegate
.
You can find out how many methods a multicast delegate is going to call.
int invocationCount = del.GetInvocationList().GetLength(0);
When you assign a method to a delegate
, the method signature does not have to match the delegate
exactly. This is called covariance
and contravariance
. Covariance makes it possible that a method has a return type that is more derived than that defined in the delegate. Contravariance permits a method that has parameter types that are less derived than those in the delegate type.
LISTING 1-77 Covariance with delegates
public delegate TextWriter CovarianceDel();
public StreamWriter MethodStream() { return null; }
public StringWriter MethodString() { return null; }
CovarianceDel del;
del = MethodStream;
del = MethodString;
Because both StreamWriter
and StringWriter
inherit from TextWriter
, you can use the CovarianceDel
with both methods.
LISTING 1-78 Contravariance with delegates
void DoSomething(TextWriter tw) { }
public delegate void ContravarianceDel(StreamWriter tw);
ContravarianceDel del = DoSomething;
MORE INFO ABOUT COVARIANCE AND CONTRAVARIANCE
1.42 Using lambda expressions
LISTING 1-79 Lambda expression to create a delegate
Calculate calc = (x, y) => x + y;
Console.WriteLine(calc(3, 4)); // Displays 7
calc = (x, y) => x * y;
Console.WriteLine(calc(3, 4)); // Displays 12
When reading this code, you can say go
or goes to
for the special lambda syntax. For example, the first lambda expression in Listing 1-79 is read as "x and y goes to adding x and y."
Lambda functions have no specific name. Because of this, they are called anonymous functions
.
LISTING 1-80 Creating a lambda expression with multiple statements
Calculate calc =
(x, y) =>
{
Console.WriteLine("Adding numbers");
return x + y;
};
int result = calc(3, 4);
// Displays
// Adding numbers
.NET Framework has a couple of built-in delegate types that you can use when declaring delegates.
Func<…>
types can be found in the System
namespace and they represent delegates that return a type and take 0 to 16 parameters.
If you want a delegate type that doesn’t return a value, you can use the System.Action
types.
LISTING 1-81 Using the Action delegate
Action<int, int> calc = (x, y) =>
{
Console.WriteLine(x + y);
};
calc(3, 4); // Displays 7
Things start to become more complex when your lambda
function starts referring to variables declared outside of the lambda
expression (or to the this
reference). Normally, when control leaves the scope of a variable, the variable is no longer valid. But what if a delegate refers to a local ariable and is then returned to the calling method? Now, the delegate has a longer life than the variable. To fix this, the compiler generates code that makes the life of the captured variable at least as long as the longest-living delegate. This is called a closure
.
1.43 Using events
A popular design pattern
(a reusable solution for a recurring problem) in application development is that of publish-subscribe
. You can subscribe to an event
and then you are notified when the publisher of the event
raises a new event
. This is used to establish loose coupling between components in an application.
LISTING 1-82 Using an Action to expose an event
public class Pub
{
public Action OnChange { get; set; }
public void Raise()
{
if (OnChange != null)
{
OnChange();
}
}
}
public void CreateAndRaise()
{
Pub p = new Pub();
p.OnChange += () => Console.WriteLine("Event raised to method 1");
p.OnChange += () => Console.WriteLine("Event raised to method 2");
p.Raise();
}
When calling CreateAndRaise
, your code creates a new instance of Pub
, subscribes to the event with two different methods and then raises the event by calling p.Raise
. The Pub
class is completely unaware of any subscribers. It just raises the event
.
If there would be no subscribers to an event
, the OnChange
property would be null
. This is why the Raise
method checks to see whether OnChange
is not null
.
Weaknesses of LISTING 1-82:
- If you change the subscribe line for method 2 to the following, you would effectively remove the first subscriber by using
= instead of +=
:
p.OnChange = () => Console.WriteLine("Event raised to method 2");
- Nothing prevents outside users of the class from raising the event. By just calling
p.OnChange()
every user of the class can raise theevent
to all subscribers.
LISTING 1-83 Using the event keyword
public class Pub
{
public event Action OnChange = delegate { };
public void Raise()
{
OnChange();
}
}
CHANGES:
- You are no longer using a public property but a public field. Normally, this would be a step back. However, with the event syntax, the compiler protects your field from unwanted access.
- An event cannot be directly assigned to (with the = instead of +=) operator. So you don’t have the risk of someone removing all previous subscriptions, as with the delegate syntax.
- No outside users can raise your event. It can be raised only by code that’s part of the class that defined the event.
- Outside users of your class can’t set the event to null; only members of your class can.
To follow the coding conventions in the .NET Framework. You should use the EventHandler
or EventHandler<T>
instead of using the Action
type for your event.
EventHandler
is declared as the following delegate:
public delegate void EventHandler(object sender, EventArgs e);
By default, it takes a sender object
and some event arguments
. The sender
is by convention the object that raised the event
(or null if it comes from a static method). By using EventHandler<T>
, you can specify the type of event
arguments you want to use.
LISTING 1-84 Custom event arguments
public class MyArgs : EventArgs
{
public MyArgs(int value)
{
Value = value;
}
public int Value { get; set; }
}
public class Pub
{
public event EventHandler<MyArgs> OnChange = delegate { };
public void Raise()
{
OnChange(this, new MyArgs(42));
}
}
public void CreateAndRaise()
{
Pub p = new Pub();
p.OnChange += (sender, e)
=> Console.WriteLine("Event raised: {0}", e.Value);
p.Raise();
}
Although the event
implementation uses a public field, you can still customize addition and removal of subscribers. This is called a custom event accessor
.
LISTING 1-85 Custom event accessor
public class Pub
{
private event EventHandler<MyArgs> onChange = delegate { };
public event EventHandler<MyArgs> OnChange
{
add
{
lock (onChange)
{
onChange += value;
}
}
remove
{
lock (onChange)
{
onChange -= value;
}
}
}
public void Raise()
{
onChange(this, new MyArgs(42));
}
}
It’s important to put a lock around adding and removing subscribers to make sure that the operation is thread safe.
Events are not delegates, they are a convenient wrapper around delegates.
LISTING 1-86 Exception when raising an event
public class Pub
{
public event EventHandler OnChange = delegate { };
public void Raise()
{
OnChange(this, EventArgs.Empty);
}
}
public void CreateAndRaise()
{
Pub p = new Pub();
p.OnChange += (sender,e )
=> Console.WriteLine("Subscriber 1 called");
p.OnChange += (sender, e)
=> { throw new Exception(); };
p.OnChange += (sender,e )
=> Console.WriteLine("Subscriber 3 called");
p.Raise();
}
// Displays
// Subscriber 1 called
LISTING 1-87 Manually raising events with exception handling
public class Pub
{
public event EventHandler OnChange = delegate { };
public void Raise()
{
var exceptions = new List<Exception>();
foreach (Delegate handler in OnChange.GetInvocationList())
{
try
{
handler.DynamicInvoke(this, EventArgs.Empty);
}
catch (Exception ex)
{
exceptions.Add(ex);
}
}
if (exceptions.Any())
{
throw new AggregateException(exceptions);
}
}
}
public void CreateAndRaise()
{
Pub p = new Pub();
p.OnChange += (sender, e)
=> Console.WriteLine("Subscriber 1 called");
p.OnChange += (sender, e)
=> { throw new Exception(); };
p.OnChange += (sender, e)
=> Console.WriteLine("Subscriber 3 called");
try
{
p.Raise();
}
catch (AggregateException ex)
{
Console.WriteLine(ex.InnerExceptions.Count);
}
}
// Displays
// Subscriber 1 called
// Subscriber 3 called
// 1
Objective summary
- Delegates are a type that defines a method signature and can contain a reference to a method.
- Delegates can be instantiated, passed around, and invoked.
- Lambda expressions, also known as anonymous methods, use the => operator and form a compact way of creating inline methods.
- Events are a layer of syntactic sugar on top of delegates to easily implement the publish-subscribe pattern.
- Events can be raised only from the declaring class. Users of events can only remove and add methods the invocation list.
- You can customize events by adding a custom event accessor and by directly using the underlying delegate type.
Objective 1.5: Implement exception handling
1.51 Handling exceptions
When an error occurs somewhere in an application, an exception
is raised.
An exception not only has a user-friendly message but it also contains the location in which the error happened and it can even store extra data, such as an address to a page that offers some help.
If an exception
goes unhandled, it will cause the current process to terminate.
LISTING 1-88 Parsing an invalid number
namespace ExceptionHandling
{
public static class Program
{
public static void Main()
{
string s = "NaN";
int i = int.Parse(s);
}
}
}
// Displays
// Unhandled Exception: System.FormatException: Input string was not in a correct format.
// at System.Number.StringToNumber(String str, NumberStyles options,
// NumberBuffer& number, NumberFormatInfo info, Boolean parseDecimal)
// at System.Number.ParseInt32(String s, NumberStyles style,
// NumberFormatInfo info)
// at System.Int32.Parse(String s)
// at ExceptionHandling.Program.Main() in c:\Users\Wouter\Documents\
// Visual Studio 2012\Projects\ExamRefProgrammingInCSharp\Chapter1\Program.cs:line 9
LISTING 1-89 Catching a FormatException
using System;
namespace ExceptionHandling
{
public static class Program
{
public static void Main()
{
while (true)
{
string s = Console.ReadLine();
if (string.IsNullOrWhiteSpace(s)) break;
try
{
int i = int.Parse(s);
break;
}
catch (FormatException)
{
Console.WriteLine("{0} is not a valid number. Please try again", s);
}
}
}
}
}
You need to surround the code that can potentially throw an exception with a try
statement. Following the try
statement, you can add several different catch blocks
. A catch block
can specify the type of the exception it wants to catch. All exceptions in the .NET Framework inherit from System.Exception
.
The catch blocks
should be specified as most-specific to least-specific because this is the order in which the runtime will examine them. When an exception
is thrown, the first matching catch block
will be executed. If no matching catch block
can be found, the exception will fall through.
LISTING 1-90 Catching different exception types
try
{
int i = int.Parse(s);
}
catch (ArgumentNullException)
{
Console.WriteLine("You need to enter a value");
}
catch (FormatException)
{
Console.WriteLine("{0} is not a valid number. Please try again", s);
}
In C# 1, you could also use a catch block
without an exception type. This could be used to catch exceptions that were thrown from other languages like C++ that don’t inherit from System.Exception
(in C++ you can throw exceptions of any type). Nowadays, each exception that doesn’t inherit from System.Exception
is automatically wrapped in a System.Runtime.CompilerServices.RuntimeWrappedException
. Since this exception inherits from System.Exception
, there is no need for the empty catch block anymore.
It’s important to make sure that your application is in the correct state when the catch block
finishes. This could mean that you need to revert changes that your try block
made before the exception was thrown.
The finally block
will execute whether an exception happens or not.
LISTING 1-91 Using a finally block
using System;
namespace ExceptionHandling
{
public static class Program
{
public static void Main()
{
string s = Console.ReadLine();
try
{
int i = int.Parse(s);
}
catch (ArgumentNullException)
{
Console.WriteLine("You need to enter a value");
}
catch (FormatException)
{
Console.WriteLine("{0} is not a valid number. Please try again", s);
}
finally
{
Console.WriteLine("Program complete.");
}
}
}
}
// Displays
// a
// a is not a valid number. Please try again
// Program complete.
When the try block
goes into an infinite loop, or in situations such as a power outage, a finally block won’t run.
Preventing the finally block
from running can be achieved by using Environment.FailFast
. When this method is called, the message (and optionally the exception) are written to the Windows application event log, and the application is terminated.
LISTING 1-92 Using Environment.FailFast
using System;
namespace ExceptionHandling
{
public static class Program
{
public static void Main()
{
string s = Console.ReadLine();
try
{
int i = int.Parse(s);
if (i == 42) Environment.FailFast("Special number entered");
}
finally
{
Console.WriteLine("Program complete.");
}
}
}
}
The line Program Complete won’t be executed if 42 is entered. Instead the application shuts down immediately.
TABLE 1-3 System.Exception properties
Name | Description |
---|---|
StackTrace | A string that describes all the methods that are currently in execution. This gives you a way of tracking which method threw the exception and how that method was reached. |
InnerException | When a new exception is thrown because another exception happened, the two are linked together with the InnerException property. |
Message | A (hopefully) human friendly message that describes the exception. |
HelpLink | A Uniform Resource Name (URN) or uniform resource locater (URL) that points to a help file. |
HResult | A 32-bit value that describes the severity of an error, the area in which the exception happened and a unique number for the exception This value is used only when crossing managed and native boundaries. |
Source | The name of the application that caused the error. If the Source is not explicitly set, the name of the assembly is used. |
TargetSite | Contains the name of the method that caused the exception. If this data is not available, the property will be null. |
Data | A dictionary of key/value pairs that you can use to store extra data for your exception. This data can be read by other catch blocks and can be used to control the processing of the exception. |
When using a catch block, you can use both an exception type and a named identifier. This way, you effectively create a variable that will hold the exception for you so you can inspect its properties.
LISTING 1-93 Inspecting an exception
using System;
namespace ExceptionHandling
{
public static class Program
{
public static void Main()
{
try
{
int i = ReadAndParse();
Console.WriteLine("Parsed: {0}", i);
}
catch (FormatException e)
{
Console.WriteLine("Message: {0}",e.Message);
Console.WriteLine("StackTrace: {0}", e.StackTrace);
Console.WriteLine("HelpLink: {0}", e.HelpLink);
Console.WriteLine("InnerException: {0}", e.InnerException);
Console.WriteLine("TargetSite: {0}", e.TargetSite);
Console.WriteLine("Source: {0}", e.Source);
}
}
private static int ReadAndParse()
{
string s = Console.ReadLine();
int i = int.Parse(s);
return i;
}
}
}
//Displays
//Message: Input string was not in a correct format.
//StackTrace: at System.Number.StringToNumber(String str, NumberStyles options,
// NumberBuffer& number, NumberFormatInfo info, Boolean parseDecimal)
// at System.Number.ParseInt32(String s, NumberStyles style,
// NumberFormatInfo info)
// at System.Int32.Parse(String s)
// at ExceptionHandling.Program.ReadAndParse() in
// c:\Users\Wouter\Documents\Visual Studio 2012\Projects\
// ExamRefProgrammingInCSharp\Chapter1\Program.cs:line 27
// at ExceptionHandling.Program.Main() in c:\Users\Wouter\Documents\
// Visual Studio 2012\Projects\ExamRefProgrammingInCSharp\
// Chapter1\Program.cs:line 10
// HelpLink:
// InnerException:
// TargetSite: Void StringToNumber(System.String, System.Globalization.NumberStyles
// , NumberBuffer ByRef, System.Globalization.NumberFormatInfo, Boolean)
// Source: mscorlib
It’s important to make sure that your finally block does not cause any exceptions.
You should only catch an exception when you can resolve the issue or when you want to log the error. Because of this, it’s important to avoid general catch blocks at the lower layers of your application. This way, you could accidentally swallow an important exception without even knowing that it happened. Logging should also be done somewhere higher up in your application. That way, you can avoid logging duplicate errors at multiple layers in your application.
1.52 Throwing exceptions
LISTING 1-94 Throwing an ArgumentNullException
public static string OpenAndParse(string fileName)
{
if (string.IsNullOrWhiteSpace(fileName))
throw new ArgumentNullException("fileName", "Filename is required");
return File.ReadAllText(fileName);
}
You should not try to reuse exception objects. Each time you throw an exception, you should create a new one, especially when working in a multithreaded environment, the stack trace of your exception can be changed by another thread.
When catching an exception, you can choose to rethrow the exception in three ways:
- Use the throw keyword without an identifier
rethrows the exception without modifying the call stack. It should be used when you don’t want any modifications to the exception. - Use the throw keyword with the original exception
reset the call stack to the current location in code. So you can’t see where the exception originally came from, and it is harder to debug the error. - Use the throw keyword with a new exception
It can be useful when you want to raise another exception to the caller of your code.
LISTING 1-95 Rethrowing an exception
try
{
SomeOperation();
}
catch (Exception logEx)
{
Log(logEx);
throw; // rethrow the original exception
}
LISTING 1-96 Throwing a new exception that points to the original one
try
{
ProcessOrder();
}
catch (MessageQueueException ex)
{
throw new OrderProcessingException("Error while processing order", ex);
}
ExceptionDispatchInfo.Throw
method in the System.Runtime.ExceptionServices
namespace can be used to throw an exception and preserve the original stack trace.
LISTING 1-97 Using ExceptionDispatchInfo.Throw
ExceptionDispatchInfo possibleException = null;
try
{
string s = Console.ReadLine();
int.Parse(s);
}
catch (FormatException ex)
{
possibleException = ExceptionDispatchInfo.Capture(ex);
}
if (possibleException != null)
{
possibleException.Throw();
}
// Displays
// Unhandled Exception: System.FormatException:
// Input string was not in a correct format.
// at System.Number.StringToNumber(String str, NumberStyles options,
// NumberBuffer& number, NumberFormatInfo info, Boolean parseDecimal)
// at System.Number.ParseInt32(String s, NumberStyles style,
// NumberFormatInfo info)
// at System.Int32.Parse(String s)
// at ExceptionHandling.Program.Main() in c:\Users\Wouter\Documents\
// Visual Studio 2012\Projects\ExamRefProgrammingInCSharp\Chapter1\
// Program.cs:line 17
//--- End of stack trace from previous location where exception was thrown ---
// at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
// at ExceptionHandling.Program.Main() in c:\Users\Wouter\Documents\
// Visual Studio 2012\Projects\ExamRefProgrammingInCSharp\Chapter1\
// Program.cs:line 6
This feature can be used when you want to catch an exception in one thread and throw it on another thread.
Exception handling changes the normal expected flow of your program. This makes it harder to read and maintain code that uses exceptions.
Using exceptions also incurs a slight performance hit. Because the runtime has to search all outer catch blocks until it finds a matching block, and when it doesn’t, has to look if a debugger is attached, it takes slightly more time to handle.
When you need to throw an exception of your own, it’s important to know which exceptions are already defined in the .NET Framework.
Some exceptions are thrown only by the runtime. You shouldn’t use those exceptions from your own code.
TABLE 1-4 Runtime exceptions in the .NET Framework
Name | Description |
---|---|
ArithmeticException | A base class for other exceptions that occur during arithmetic operations. |
ArrayTypeMismatchException | Thrown when you want to store an incompatible element inside an array. |
DivideByZeroException | Thrown when you try to divide a value by zero. |
IndexOutOfRangeException | Thrown when you try to access an array with an index that’s less than zero or greater than the size of the array. |
InvalidCastException | Thrown when you try to cast an element to an incompatible type. |
NullReferenceException | Thrown when you try to reference an element that’s null. |
OutOfMemoryException | Thrown when creating a new object fails because the CLR doesn’t have enough memory available. |
OverflowException | Thrown when an arithmetic operation overflows in a checked context. |
StackOverflowException | Thrown when the execution stack is full. This can happen in a recursive operation that doesn’t exit. |
TypeInitializationException | Thrown when a static constructor throws an exception that’s goes unhandled. |
TABLE 1-5 Popular exceptions in the .NET Framework
Name | Description |
---|---|
Exception | The base class for all exceptions. Try avoiding throwing and catching this exception because it’s too generic. |
ArgumentException | Throw this exception when an argument to your method is invalid. |
ArgumentNullException | A specialized form of ArgumentException that you can throw when one of your arguments is null and this isn’t allowed. |
ArgumentOutOfRangeException | A specialized form of ArgumentException that you can throw when an argument is outside the allowable range of values. |
FormatException | Throw this exception when an argument does not have a valid format. |
InvalidOperationException | Throw this exception when a method is called that’s invalid for the object’s current state. |
NotImplementedException | This exception is often used in generated code where a method has not been implemented yet. |
NotSupportedException | Throw this exception when a method is invoked that you don’t support. |
ObjectDisposedException | Throw when a user of your class tries to access methods when Dispose has already been called. |
You should avoid directly using the Exception
base class both when catching and throwing exceptions. Instead you should try to use the most specific exception available.
1.53 Creating custom exceptions
A custom exception is especially useful when developers working with your code are aware of those exceptions and can handle them in a more specific way than the framework exceptions.
A custom exception should inherit from System.Exception
. You need to provide at least a parameterless constructor. It’s also a best practice to add a few other constructors: one that takes a string
, one that takes both a string
and an exception
, and one for serialization
.
LISTING 1-98 Creating a custom exception
[Serializable]
public class OrderProcessingException : Exception, ISerializable
{
public OrderProcessingException(int orderId)
{
OrderId = orderId;
this.HelpLink = "http://www.mydomain.com/infoaboutexception";
}
public OrderProcessingException(int orderId, string message)
: base(message)
{
OrderId = orderId;
this.HelpLink = "http://www.mydomain.com/infoaboutexception";
}
public OrderProcessingException(int orderId, string message,
Exception innerException)
: base(message, innerException)
{
OrderId = orderId;
this.HelpLink = "http://www.mydomain.com/infoaboutexception";
}
protected OrderProcessingException(SerializationInfo info, StreamingContext context)
{
OrderId = (int)info.GetValue("OrderId", typeof(int));
}
public int OrderId { get; private set; }
public void GetObjectData(SerializationInfo info, StreamingContext context)
{
info.AddValue("OrderId", OrderId, typeof(int));
}
}
By convention, you should use the Exception
suffix in naming all your custom exceptions.
It’s also important to add the Serializable
attribute, which makes sure that your exception can be serialized and works correctly across application domains (for example, when a web service returns an exception).
You should never inherit from System.ApplicationException
. The original idea was that all C# runtime exceptions should inherit from System.Exception and all custom exceptions from System.ApplicationException. However, because the .NET Framework doesn’t follow this pattern, the class became useless and lost its meaning.
Objective summary
- In the .NET Framework, you should use exceptions to report errors instead of error codes.
- Exceptions are objects that contain data about the reason for the exception.
- You can use a try block with one or more catch blocks to handle different types of exceptions.
- You can use a finally block to specify code that should always run after, whether or not an exception occurred.
- You can use the throw keyword to raise an exception.
- You can define your own custom exceptions when you are sure that users of your code will handle it in a different way. Otherwise, you should use the standard .NET Framework exceptions.
Chapter summary
- Multithreading can help you create programs that are responsive and scalable. You can use the TPL, the Parallel class, and PLINQ for this. The new async/await keywords help you write asynchronous code.
- In a multithreaded environment, it’s important to manage synchronization of shared data. You can do this by using the lock statement.
- C# offers statements for making decisions—if, switch, conditional operator (?) and null-coalescing operator (??)—iterating (for, foreach, while, do-while), and jump statements (break, continue, goto, return and throw).
- Delegates are objects that point to a method and can be used to invoke the method. Lambda expressions are a shorthand syntax for creating anonymous methods inline.
- Events are a layer on top of delegates that help you with creating a publish-subscribe architecture.
- Exceptions are the preferred way to work with errors in the .NET Framework. You can throw exceptions, catch them, and run code in a finally block.
网友评论