Skip to content
This repository has been archived by the owner on Aug 31, 2021. It is now read-only.

Commit

Permalink
[C#] Implement new Concept Exercise: nullability (#992)
Browse files Browse the repository at this point in the history
[C#] Implement new Concept Exercise: nullability (#992)

Co-authored-by: Erik Schierboom <erik_schierboom@hotmail.com>
Co-authored-by: Silvano Cerza <3314350+silvanocerza@users.noreply.github.com>
Co-authored-by: Jeremy Walker <jez.walker@gmail.com>
Co-authored-by: wolf99 <wolf99@users.noreply.github.com>
Co-authored-by: Mike May <mikemay.mdamay@googlemail.com>
Co-authored-by: mikedamay <mikemay@blueyonder.co.uk>
Co-authored-by: Derk-Jan Karrenbeld <derk-jan+github@karrenbeld.info>
Co-authored-by: mikedamay <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Eli Flanagan <efx@users.noreply.github.com>
Co-authored-by: wolf99 <wol99@users.noreply.github.com>
Co-authored-by: Johan Berg <47738833+bergjohan@users.noreply.github.com>
Co-authored-by: Rob Keim <robkeim@gmail.com>
Co-authored-by: Sascha Mann <git@mail.saschamann.eu>
Co-authored-by: wolf99 <281700+wolf99@users.noreply.github.com>
  • Loading branch information
15 people authored Apr 23, 2020
1 parent 796afdb commit d5241ae
Show file tree
Hide file tree
Showing 13 changed files with 435 additions and 0 deletions.
11 changes: 11 additions & 0 deletions languages/csharp/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,17 @@
"exceptions"
]
},
{
"slug": "nullability",
"uuid": "370e7e54-6a96-11ea-bc55-0242ac130003",
"concepts": ["nullability"],
"prerequisites": [
"strings",
"exceptions",
"for-loops",
"memory-allocation"
]
},
{
"slug": "strings",
"uuid": "eda235a5-37de-4fb6-a5c5-cf7aa2309987",
Expand Down
60 changes: 60 additions & 0 deletions languages/csharp/exercises/concept/nullability/.docs/after.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
Sometimes we need to make it so that variables have no particular
value, i.e. they are empty. In C#, this corresponds to the [literal
`null`][null-keyword].

In this exercise, we saw the definition of [nullable
types][nullable-types-tutorial], and how the compiler and runtime of
C# help us dealing with `null` values.

At compilation time, [the operator `?`][nullable-reference-types] will
declare a variable as being *nullable*. The compiler will then try to
help us avoiding calling methods on possibly `null` variables, by
raising warnings. You can use [the operator
`!`][null-forgiving-operator] to avoid warnings in places we are sure
a nullable variable is not null, but the compiler cannot detect
it.

We can also use [the operators `??` and
`??=`][null-coalescing-operator] to provide a default value for a
nullable variable and [the operator `?.`][null-conditional-operator]
to chain accesses to methods, properties or attributes of potentially
`null` objects on an expression that evaluates to `null` instead of
throwing a `NullReferenceException`.

```csharp
string? userName = null; // declares `userName` as a nullable string
Console.WriteLine(userName?.Length); // prints: "null", since `userName` is `null`
Console.WriteLine(userName ?? "default"); // prints: "default", since `userName` is `null`
userName ??= "unknownUser"; // sets `userName` to "unknownUser" since its
// value was null
Console.WriteLine(userName!.Length); // prints "11" and avoids a compiler warning
```

At run time, calling any method or property on a
`null` value throws a `NullReferenceException` exception.
That is why it is important to always check if a nullable variable
is `null` before calling methods on its value.

# Important changes in C# 8.0

Sometimes, we need to make sure that some variables are never
`null`. This simplifies code because it won't need to handle
`NullReferenceException`s or provide extra provisions for `null`
values.

Before C# 8.0, reference types were nullable by default. For example,
a variable of type `string` may contain a `null` value, even it is not
declared as `string?`.

For more information, refer to [this
][nullable-csharp-8] and [this][nullable-reference-types-tutorial] documents.

[null-keyword]: https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/null
[nullable-types-tutorial]: https://csharp.net-tutorials.com/data-types/nullable-types/
[nullable-reference-types]: https://docs.microsoft.com/en-us/dotnet/csharp/nullable-references
[nullable-csharp-8]: https://docs.microsoft.com/en-us/dotnet/csharp/nullable-references
[null-forgiving-operator]: https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/operators/null-forgiving
[null-coalescing-operator]: https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/operators/null-coalescing-operator
[null-conditional-operator]: https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/operators/member-access-operators#null-conditional-operators--and-
[nullable-reference-types-tutorial]: https://docs.microsoft.com/en-us/archive/msdn-magazine/2018/february/essential-net-csharp-8-0-and-nullable-reference-types
38 changes: 38 additions & 0 deletions languages/csharp/exercises/concept/nullability/.docs/hints.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
### General

The following reference documentation may help you finishing this exercice:

- [Null keyword][null-keyword]: reference documentation for `null` keyword.
- [Nullable types tutorial][nullable-types-tutorial]: basic tutorial on how to work with nullable types.
- [Nullable reference types][nullable-reference-types]: how to use nullable reference types.
- [Null-coalescing operator][null-coalescing-operator]: explains how the null-coalescing operator works.
- [Null-conditional operator][null-conditional-operator]: explains how the null-conditional operator works.
- [Nullable reference types tutorial][nullable-reference-types-tutorial]: tutorial on nullable reference types.

### 1. Badge.Label

* Which method from the `string` class may help you making the
department name [uppercase][toupper]?

### 2. Badge.Label optional parameters

* Do not forget to add `GUEST` as the department name if there's no
provided department name.

### 2. Badge.PrintLabel

* Which method from the `string` class may help you to extract a
[substring][substring]
of a string?
* Do all slices always have the same length? If not, how can you handle
this case?
* Do not forget the `prefix` should fit on the `maximumWidth`

[null-keyword]: https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/null
[nullable-types-tutorial]: https://csharp.net-tutorials.com/data-types/nullable-types/
[null-coalescing-operator]: https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/operators/null-coalescing-operator
[null-conditional-operator]: https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/operators/conditional-operator
[nullable-reference-types]: https://docs.microsoft.com/en-us/dotnet/csharp/nullable-references
[nullable-reference-types-tutorial]: https://docs.microsoft.com/en-us/archive/msdn-magazine/2018/february/essential-net-csharp-8-0-and-nullable-reference-types
[substring]: https://docs.microsoft.com/en-us/dotnet/api/system.string.substring?view=netframework-4.8
[toupper]: https://docs.microsoft.com/en-us/dotnet/api/system.string.toupper?view=netframework-4.8xv
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
In this exercise you'll be writing code to helping printing name
badges for factory employees.

Employees have an ID, name and department name. Badge labels are
formatted as follows: `"[id] - [name] - [DEPARTMENT]"`

### 1. Generate an employee's badge label

Implement the `Badge.Label()` method to return an employee's badge label:

```csharp
Badge.Label(734, "Ernest Johnny Payne", "Strategic Communication");
// => "[734] - Ernest Johnny Payne - STRATEGIC COMMUNICATION"
```

### 2. Handle the optional parts of a badge label

The ID and department name are optional. If someone does not have an
ID, do not print this part of the label. If someone has no
department, just print `GUEST` as their department name.

### 3. Generate the actual text to be print on the badge

Implement the `Badge.PrintLabel()` method to return an employee's
badge label that can fit on a badge with a given width. An optional
prefix may be added on the beginning of each line:

```csharp
Badge.PrintLabel(" > ", "[734] - Ernest Johnny Payne - STRATEGIC COMMUNICATION", 30);
// => " > [734] - Ernest Johnny Payne - \n> STRATEGIC COMMUNICATION"
//
// => Printed label:
//
// > [734] - Ernest Johnny Payne -
// > STRATEGIC COMMUNICATION
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
In C#, the `null` literal is used to denote the absence of a value. A
*nullable* type is a type that allows `null` values. By default
reference types are nullable and value types are non nullable. The
`?` suffix to a variable's type makes it nullable if it is not. From
C# 8.0 on, one can [make reference types non nullable by
default][nullable-csharp-8], just like value types.

```csharp
int? nullable = 5;
nullable = null;

int nonNullable = 5;

// Would result in compile error as type is not nullable
// nonNullable = null;
```

A common mistake is to access a member (e.g. a method or property) on a nullable
variable whose value is `null`. This will cause the C# runtime
to raise a `NullReferenceException`.

The C# compiler can help us avoiding such errors. If we try to
compile the following code:

```csharp
string? userName = ...;

...

Console.WriteLine(userName.Length);
```

The compiler will give the following warning message:

```
Dereference of a possibly null reference. (CS8602)
```

A good practice is testing if a nullable value is not `null` before
trying to do something with it. There are some cases however, where we
know for sure that, given the context, a variable cannot be `null`.

In order to dismiss the warning, we can use the operator `!`:

```csharp
Console.WriteLine(userName!.Length);
```

Finally, sometimes, we want to provide a default value to a variable,
in case it is `null`. We know how to use an `if` block to check
that. However, the more nullable variables we need to check, the more
cumbersome it gets.

The `??` and `?.` operators are a simple shortcuts for that:

```csharp
// prints: "default" if `userName` is `null`
Console.WriteLine(userName ?? "default");

// This code is equivalent to:
string userNameValue;
if (userName == null)
{
userNameValue = "default";
}
else
{
userNameValue = userName;
}
Console.WriteLine(userNameValue);



// prints: `null` if `userName` is `null`
Console.WriteLine(userName?.Length);

// This code is equivalent to
int userNameLength;
if (userName == null)
{
userNameLength = 0;
}
else
{
userNameLength = userName.Length;
}
Console.WriteLine(userNameLength);
```

With `A ?? B`, the C# runtime replaces the expression `A` with `B` if
`A` evaluates to `null`. The operator `?.` behaves similarly, the C#
runtime evaluates the expression `A?.B` by `null` if `A` is null,
without throwing a `NullReferenceException`. It executes `A.B`
otherwise.

[nullable-csharp-8]: https://docs.microsoft.com/en-us/dotnet/csharp/nullable-references
27 changes: 27 additions & 0 deletions languages/csharp/exercises/concept/nullability/.meta/Example.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
using System;

public static class Badge
{
public static string Label(int? id, string name, string? department)
{
var idLabel = (id == null ? "" : $"[{id}] - ");
return $"{idLabel}{name} - {department?.ToUpper() ?? "GUEST"}";
}

public static string PrintLabel(string? prefix,
string label,
int maximumWidth)
{
maximumWidth -= prefix?.Length ?? 0;

var output = "";
for (int i = 0; i < label.Length; i += maximumWidth)
{
output += (prefix ?? "") +
label.Substring(i, Math.Min(maximumWidth,
label.Length - i)) + "\n";
}
return output.TrimEnd();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"authors": [
{
"github_username": "maurelio1234",
"exercism_username": "maurelio1234"
}
]
}
50 changes: 50 additions & 0 deletions languages/csharp/exercises/concept/nullability/.meta/design.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# Design

## Goal

The goal of this exercise is to introduce the student to the concept of [Nullability in C#][null-keyword].

## Learning objectives

- Know of the existence of the `null` literal.
- Know what a `NullReferenceException` is and when it is thrown.
- Know how to compare a value to `null`.
- Know the difference between value and reference types regarding nullability, especially pre C# 8.0.
- Know how to define nullable reference and value types.
- Know about the null-related operators (`!`, `?`, `??`).
- Know about basic null checking by the compiler.

## Out of scope

- Nullable attributes.
- In-depth discussion of null checking by the compiler.
- Enabling C# 8 null checking.
- Casting using the `as` or `is` operator or using pattern matching.

## Concepts

This Concepts Exercise's Concepts are:

- `nullability`: know of the existence of the `null` literal; know what a `NullReferenceException` is and when it is thrown; know how to compare a value to `null`; know the difference between value and reference types regarding nullability; know how to define nullable reference and value types; know about the null-related operators; know about basic null checking by the compiler.

## Prerequisites

This Concept Exercise's prerequisites Concepts are:

- `strings`: strings will be compared to `null` and basic methods from strings will be called.
- `basics`: integers will be compared to `null`, arithmetic operations will be performed on integers, variables will be introduced and updated.
- `exceptions`: explain how a `NullReferenceException` is thrown when accessing a `null` value.
- `for-loops`: strings will be processed and constructed iteratively.
- `memory-allocation`: reference and value types will be used in their nullable and non-nullable variants.

## Representer

This exercise does not require any specific representation logic to be added to the [representer][representer].

## Analyzer

This exercise does not require any specific logic to be added to the [analyzer][analyzer].

[analyzer]: https://github.com/exercism/csharp-analyzer
[representer]: https://github.com/exercism/csharp-representer
[null-keyword]: https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/null
16 changes: 16 additions & 0 deletions languages/csharp/exercises/concept/nullability/Nullability.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using System;

public static class Badge
{
public static string Label(int? id, string name, string? department)
{
throw new NotImplementedException("Please implement the Badge.Label method");
}

public static string PrintLabel(string? prefix,
string label,
int maximumWidth)
{
throw new NotImplementedException("Please implement the Badge.PrintLabel method");
}
}
14 changes: 14 additions & 0 deletions languages/csharp/exercises/concept/nullability/Nullability.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.5.0" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" />
</ItemGroup>

</Project>
Loading

0 comments on commit d5241ae

Please sign in to comment.