This repository has been archived by the owner on Aug 31, 2021. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 162
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[C#] Add exercise User-defined Exceptions (#1617)
* csharp/user-exceptions initial upload * csharp/user-exceptions initial code/instructions complete * csharp/user-exceptions tweak to design.md * csharp/user-exceptions proofing * csharp/user-exceptions proofing * csharp/user-exceptions after review * csharp/user-exceptions after review * csharp/user-exceptions intro and after * csharp/user-exceptions tweaking docs * Update languages/csharp/exercises/concept/user-defined-exceptions/.meta/Example.cs Co-authored-by: Erik Schierboom <erik_schierboom@hotmail.com> * Apply suggestions from code review Co-authored-by: Erik Schierboom <erik_schierboom@hotmail.com> * csharp/user-exceptions more review changes * csharp/user-exceptions substantially complete * csharp/user-exceptions removed text not required for publication * csharp/user-exceptions added pre-requisites * csharp/user-exceptions updated out-of-date instructions * csharp/user-exceptions updated config * Apply suggestions from code review Co-authored-by: Erik Schierboom <erik_schierboom@hotmail.com> * Update languages/csharp/exercises/concept/user-defined-exceptions/.docs/after.md Co-authored-by: Erik Schierboom <erik_schierboom@hotmail.com> * csharp/user-exceptions late review points * Update after.md * copy/paste error Co-authored-by: Erik Schierboom <erik_schierboom@hotmail.com>
- Loading branch information
1 parent
2b62cd8
commit 3945e09
Showing
11 changed files
with
363 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
50 changes: 50 additions & 0 deletions
50
languages/csharp/exercises/concept/user-defined-exceptions/.docs/after.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
A user-defined exception is any class defined in your code that is derived from `System.Exception`. It is subject to all the rules of class inheritance but in addition the compiler and language runtime treat such classes in a special way allowing their instances to be thrown and caught outside the normal control flow as discussed in the `exceptions` exercise. User-defined exceptions can be used in every way like runtime and Microsoft Base Class Library exceptions. | ||
|
||
This special treatment applies only to `Exception`-derived classes. You cannot throw instances of any other type. | ||
|
||
User-defined exceptions are often used to carry extra information such as a message and other relevant data to be made available to the catching routines. This can then be recorded in logs, reported to a user or perhaps used in a retry operation. `System.Exception` has convenient data members and appropriate constructors to hold a message and the "inner" exception. | ||
|
||
By convention exception class names end with "Exception", e.g. `MyTerribleException`. | ||
|
||
Whilst using user-defined exceptions to wrap and enhance third party exceptions is a frequently seen pattern, the general advice is not to use them outside of this use case too liberally in your own code. It is considered an anti-pattern. There are challenges to this view and you can see both sides of the argument in this [Stack Exchange post][se-exceptions]. | ||
|
||
This [article][create-user-defined-exceptions] is a good introduction to user-defined exceptions. | ||
|
||
As part of their guidance on [creating and throwing exceptions][exceptions-guidance] the .NET team recommend that user defined exceptions have a number of [convenience constructors][convenience-constructors]. For most purposes the combination illustrated below is appropriate. | ||
|
||
```csharp | ||
public class IncompleteExerciseException : System.Exception | ||
{ | ||
public IncompleteExerciseException() : base() { } | ||
public IncompleteExerciseException(string message) : base(message) { } | ||
public IncompleteExerciseException(string message, System.Exception inner) : base(message, inner) { } | ||
} | ||
``` | ||
|
||
## Exception Filters | ||
|
||
`when` is the keyword in filtering exceptions. It is placed after the catch | ||
statement and can take a boolean expression containing any values in scope at the time. They don't just have to be members of the exception itself. If the type of the exception matches and the expression evaluates to true then the block associated with that `catch` statement is executed otherwise the next `catch` statement, if any, is checked. | ||
|
||
```csharp | ||
try | ||
{ | ||
// do stuff | ||
} | ||
catch (Exception ex) when (ex.Message != "") | ||
{ | ||
// output the message when it is not empty | ||
} | ||
catch (Exception ex) | ||
{ | ||
// show stack trace or something. | ||
} | ||
``` | ||
|
||
- This [Exception filters][exception-filters] article shows how to filter exceptions. | ||
|
||
[create-user-defined-exceptions]: https://docs.microsoft.com/en-us/dotnet/standard/exceptions/how-to-create-user-defined-exceptions | ||
[exception-filters]: https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/when | ||
[se-exceptions]: https://softwareengineering.stackexchange.com/questions/189222/are-exceptions-as-control-flow-considered-a-serious-antipattern-if-so-why | ||
[exceptions-guidance]: https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/exceptions/creating-and-throwing-exceptions | ||
[convenience-constructors]: https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/exceptions/creating-and-throwing-exceptions#defining-exception-classes |
22 changes: 22 additions & 0 deletions
22
languages/csharp/exercises/concept/user-defined-exceptions/.docs/hints.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
## General | ||
|
||
- [Create user-defined exceptions][create-user-defined-exceptions]: how to create user-defined exceptions | ||
- [Exception filters][exception-filters]: how to filter user-defined-exceptions. | ||
|
||
## 1. Complete the definition of the user-defined exception `CalculationException` | ||
|
||
- The constructors of the `Exception` base class are discussed [here][exception-constructors]. | ||
|
||
## 2. Implement the `Multiply()` method | ||
|
||
- `try-catch` blocks are discussed [here][try-catch] as well as in the `exceptions` exercise. | ||
|
||
## 4. Implement the `TestMultiplication()` method | ||
|
||
- This [article][try-catch-when] has an example of the use of `when`. | ||
|
||
[create-user-defined-exceptions]: https://docs.microsoft.com/en-us/dotnet/standard/exceptions/how-to-create-user-defined-exceptions | ||
[exception-filters]: https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/when | ||
[exception-constructors]: https://docs.microsoft.com/en-us/dotnet/api/system.exception.-ctor?view=netcore-3.1 | ||
[try-catch]: https://docs.microsoft.com/en-us/dotnet/standard/exceptions/how-to-use-the-try-catch-block-to-catch-exceptions | ||
[try-catch-when]: https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/try-catch |
56 changes: 56 additions & 0 deletions
56
languages/csharp/exercises/concept/user-defined-exceptions/.docs/instructions.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
While working at _Instruments of Texas_, you are tasked to work on an experimental calculator written in C#. You are building a test harness to verify a number of calculator functions starting with multiplication. You will see that there is particular concern when the two operands of the multiplication are negative. | ||
|
||
The `Calculator` class has been provided for you and should not be modified. | ||
|
||
## 1. Complete the definition of the user-defined exception `CalculationException` | ||
|
||
Complete the definition of the constructor of `CalculationException` which will need to store (wrap) any exception thrown by the calculator as well as the operands that are being processed at the time the exception is thrown. | ||
|
||
## 2. Handle overflow conditions in the calculator and provide enhanced information to the caller | ||
|
||
Implement the `CalculatorTestHarness.Multiply()` method, which should call the `Calculator.Multiply()` method of the `Calculator` instance passed to the constructor. | ||
passing in x and y integer values. If an overflow occurs in the `Calculator.Multiply()` method, it will throw an `OverflowException`. This exception should be caught in the `CalculatorTestHarness.Multiply()` method and wrapped in a `CalculationException` and the x and y values being passed around should be stored as the exception's operands. The newly created `CalculationException` object should be thrown. You can ignore the value returned by the `Calculator.Multiply()` method if it is successful. | ||
|
||
```csharp | ||
var cth = new CalculatorTestHarness(new Calculator()); | ||
cth.Multiply(Int32.MaxValue, Int32.MaxValue); | ||
// => throws an instance of CalculationException | ||
var cth2 = new CalculatorTestHarness(new Calculator()); | ||
cth2.Multiply(3, 2); | ||
// => silently exits | ||
``` | ||
|
||
## 3. Test the multiplication operation for valid inputs | ||
|
||
Implement the `CalculatorTestHarness.TestMultiplication()` method which takes two integers and calls the `CalculatorTestHarness.Multiply()` method. `"Multiply succeeded"` is returned. | ||
|
||
```csharp | ||
var cth = new CalculatorTestHarness(new Calculator()); | ||
cth.Multiply(6, 7); | ||
// => "Multiply succeeded" | ||
``` | ||
|
||
## 4. Test the multiplication operation for negative inputs | ||
|
||
Modify the `CalculatorTestHarness.TestMultiplication()` method so that `"Multiply failed for negative operands. <INNER_EXCEPTION_MESSAGE>"`is returned if both integer arguments are negative (less than zero). | ||
|
||
The `<INNER_EXCEPTION_MESSAGE>` placeholder should be replaced with the `CalculationException`'s inner exception's message. | ||
|
||
```csharp | ||
var cth = new CalculatorTestHarness(new Calculator()); | ||
cth.TestMultiplication(-2, -Int32.MaxValue); | ||
// => "Multiply failed for negative operands. " + innerException.Message | ||
``` | ||
|
||
## 5. Test the multiplication operation for positive inputs | ||
|
||
Modify the `CalculatorTestHarness.TestMultiplication()` method so that `"Multiply failed for mixed or positive operands. <INNER_EXCEPTION_MESSAGE>"` is returned if at least one of the integer arguments is not negative. | ||
|
||
The `<INNER_EXCEPTION_MESSAGE>` placeholder should be replaced with the `CalculationException`'s inner exception's message. | ||
|
||
```csharp | ||
var cth = new CalculatorTestHarness(new Calculator()); | ||
cth.TestMultiplication(Int32.MaxValue, Int32.MaxValue); | ||
// => "Multiply failed for mixed or positive operands. " + innerException.Message | ||
``` |
23 changes: 23 additions & 0 deletions
23
languages/csharp/exercises/concept/user-defined-exceptions/.docs/introduction.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
A user-defined exception is any class defined in your code that is derived from `System.Exception`. It is subject to all the rules of class inheritance but in addition the compiler and language runtime treat such classes in a special way allowing their instances to be thrown and caught outside the normal control flow as discussed in the `exceptions` exercise. | ||
|
||
User-defined exceptions are often used to carry extra information such as a message and other relevant data to be made available to the catching routines. | ||
|
||
## Exception Filters | ||
|
||
`when` is the key word in filtering exceptions. It is placed after the catch | ||
statement and can take a boolean expression containing any values in scope at the time. If the expression evaluates to true then the block associated with that `catch` statement is executed otherwise the next `catch` statement, if any, is checked. | ||
|
||
```csharp | ||
try | ||
{ | ||
// do stuff | ||
} | ||
catch (Exception ex) when (ex.Message != "") | ||
{ | ||
// output the message when it is not empty | ||
} | ||
catch (Exception ex) | ||
{ | ||
// show stack trace or something. | ||
} | ||
``` |
67 changes: 67 additions & 0 deletions
67
languages/csharp/exercises/concept/user-defined-exceptions/.meta/Example.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
using System; | ||
|
||
public class CalculationException : Exception | ||
{ | ||
public CalculationException(int operand1, int operand2, string message, Exception inner) : base(message, inner) | ||
{ | ||
Operand1 = operand1; | ||
Operand2 = operand2; | ||
} | ||
|
||
public int Operand1 { get; } | ||
public int Operand2 { get; } | ||
} | ||
|
||
public class CalculatorTestHarness | ||
{ | ||
private Calculator calculator; | ||
|
||
public CalculatorTestHarness(Calculator calculator) | ||
{ | ||
this.calculator = calculator; | ||
} | ||
|
||
public string TestMultiplication(int x, int y) | ||
{ | ||
try | ||
{ | ||
Multiply(x, y); | ||
return "Multiply succeeded"; | ||
} | ||
catch (CalculationException cex) when (cex.Operand1 < 0 && cex.Operand2 < 0) | ||
{ | ||
return "Multiply failed for negative operands. " + cex.InnerException.Message; | ||
} | ||
catch (CalculationException cex) | ||
{ | ||
return "Multiply failed for mixed or positive operands. " + cex.InnerException.Message; | ||
} | ||
} | ||
|
||
public void Multiply(int x, int y) | ||
{ | ||
try | ||
{ | ||
calculator.Multiply(x, y); | ||
} | ||
catch (OverflowException ofex) | ||
{ | ||
throw new CalculationException(x, y, string.Empty, ofex); | ||
} | ||
} | ||
} | ||
|
||
|
||
// Please do not modify the code below. | ||
// If there is an overflow in the multiplication operation | ||
// then a System.OverflowException is thrown. | ||
public class Calculator | ||
{ | ||
public int Multiply(int x, int y) | ||
{ | ||
checked | ||
{ | ||
return x * y; | ||
} | ||
} | ||
} |
9 changes: 9 additions & 0 deletions
9
languages/csharp/exercises/concept/user-defined-exceptions/.meta/config.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
{ | ||
"authors": [ | ||
{ | ||
"github_username": "mikedamay", | ||
"exercism_username": "mikedamay" | ||
} | ||
], | ||
"forked_from": ["elixir/errors"] | ||
} |
27 changes: 27 additions & 0 deletions
27
languages/csharp/exercises/concept/user-defined-exceptions/.meta/design.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
## Learning objectives | ||
|
||
- Know how to define a user-defined exception. | ||
- Know how to use exception filtering. | ||
- Know that using errors as control logic is an anti-pattern | ||
|
||
## Out of scope | ||
|
||
- Memory and performance characteristics. | ||
|
||
## Concepts | ||
|
||
This Concepts Exercise's Concepts are: | ||
|
||
- `user-defined-exceptions`: know how to define a user-defined exception. | ||
- `exception-filtering`: know how to use exception filtering. | ||
|
||
## Prequisites | ||
|
||
This Concept Exercise's prerequisites Concepts are: | ||
|
||
- `exceptions`: know how to work with exceptions. | ||
- `inheritance`: inheriting from the `Exception` class for the custom exception. | ||
- `strings`: converting an into a string | ||
- `conditionals`: use of simple `if`/`else` | ||
- `arithmetic-overflow` | ||
- `signed-integers`: `Int32.MaxValue` |
13 changes: 13 additions & 0 deletions
13
languages/csharp/exercises/concept/user-defined-exceptions/UserDefinedExceptions.csproj
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
<Project Sdk="Microsoft.NET.Sdk"> | ||
|
||
<PropertyGroup> | ||
<TargetFramework>netcoreapp3.1</TargetFramework> | ||
</PropertyGroup> | ||
|
||
<ItemGroup> | ||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.6.1" /> | ||
<PackageReference Include="xunit" Version="2.4.1" /> | ||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" /> | ||
</ItemGroup> | ||
|
||
</Project> |
36 changes: 36 additions & 0 deletions
36
languages/csharp/exercises/concept/user-defined-exceptions/UserDefinedExceptionsTests.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
using System; | ||
using Xunit; | ||
|
||
public class UserDefinedExceptionsTests | ||
{ | ||
[Fact] | ||
public void Multiply_with_overflow_throws_calculationexception() | ||
{ | ||
var cth = new CalculatorTestHarness(new Calculator()); | ||
|
||
Assert.Throws<CalculationException>(() => cth.Multiply(Int32.MaxValue, Int32.MaxValue)); | ||
} | ||
|
||
[Fact(Skip = "Remove this Skip property to run this test")] | ||
public void TestMultiplication_with_negative_overflow() | ||
{ | ||
var cth = new CalculatorTestHarness(new Calculator()); | ||
Assert.Equal("Multiply failed for negative operands. Arithmetic operation resulted in an overflow.", | ||
cth.TestMultiplication(-2, -Int32.MaxValue)); | ||
} | ||
|
||
[Fact(Skip = "Remove this Skip property to run this test")] | ||
public void TestMultiplication_with_positive_overflow() | ||
{ | ||
var cth = new CalculatorTestHarness(new Calculator()); | ||
Assert.Equal("Multiply failed for mixed or positive operands. Arithmetic operation resulted in an overflow.", | ||
cth.TestMultiplication(Int32.MaxValue, Int32.MaxValue)); | ||
} | ||
|
||
[Fact(Skip = "Remove this Skip property to run this test")] | ||
public void Call_TestMultiplication_with_success() | ||
{ | ||
var cth = new CalculatorTestHarness(new Calculator()); | ||
Assert.Equal("Multiply succeeded", cth.TestMultiplication(6, 7)); | ||
} | ||
} |
47 changes: 47 additions & 0 deletions
47
languages/csharp/exercises/concept/user-defined-exceptions/UserDefinedExceptons.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
using System; | ||
|
||
public class CalculationException : Exception | ||
{ | ||
public CalculationException(int operand1, int operand2, string message, Exception inner) | ||
// TODO: complete the definition of the constructor | ||
{ | ||
} | ||
|
||
public int Operand1 { get; } | ||
public int Operand2 { get; } | ||
} | ||
|
||
public class CalculatorTestHarness | ||
{ | ||
private Calculator calculator; | ||
|
||
public CalculatorTestHarness(Calculator calculator) | ||
{ | ||
this.calculator = calculator; | ||
} | ||
|
||
public string TestMultiplication(int x, int y) | ||
{ | ||
throw new NotImplementedException("Please implement the CalculatorTestHarness.TestMultiplication() method"); | ||
} | ||
|
||
public void Multiply(int x, int y) | ||
{ | ||
throw new NotImplementedException("Please implement the CalculatorTestHarness.Multiply() method"); | ||
} | ||
} | ||
|
||
|
||
// Please do not modify the code below. | ||
// If there is an overflow in the multiplication operation | ||
// then a System.OverflowException is thrown. | ||
public class Calculator | ||
{ | ||
public int Multiply(int x, int y) | ||
{ | ||
checked | ||
{ | ||
return x * y; | ||
} | ||
} | ||
} |