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

[C#] constants new exercise #1877

Merged
merged 50 commits into from
Aug 6, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
ddf392e
csharp/tuples first shot
mikedamay Jun 9, 2020
8b69ccc
csharp/tuples aligned with latest document formatting
mikedamay Jun 9, 2020
5315785
tweaked design doc
mikedamay Jun 9, 2020
e32a4f0
csharp/tuples after.md
mikedamay Jun 10, 2020
c34f998
Merge remote-tracking branch 'origin/csharp/tuples' into csharp/tuples
mikedamay Jun 10, 2020
c2b7588
csharp/tuples introduction.md
mikedamay Jun 10, 2020
7d3d87e
csharp/tuples tweaks to introduction.md
mikedamay Jun 10, 2020
d9a04ea
csharp/tuples tweak to introduction.md
mikedamay Jun 10, 2020
9e828ed
csharp/tuples fixed typos in instructions.md
mikedamay Jun 10, 2020
e0b4444
added exercise template
mikedamay Jun 18, 2020
a9861e3
csharp/tuples tweaks
mikedamay Jun 20, 2020
8c16740
Apply suggestions from code review
mikedamay Jun 23, 2020
174e65e
csharp/tuples after review
mikedamay Jun 23, 2020
b3bf8df
csharp/tuples after review
mikedamay Jun 24, 2020
f31c1aa
Merge branch 'master' into csharp/tuples
mikedamay Jun 24, 2020
bde2ce9
Apply suggestions from code review
mikedamay Jun 24, 2020
a49ca43
csharp/tuples late review
mikedamay Jun 24, 2020
87e9a63
csharp/tuples missed reveiew point
mikedamay Jun 24, 2020
a1de61a
csharp/tuples missed reveiew point
mikedamay Jun 24, 2020
e19e1ec
Apply suggestions from code review
mikedamay Jun 24, 2020
80caa85
Merge branch 'csharp/tuples'
mikedamay Jun 25, 2020
b551f13
Merge remote-tracking branch 'canonical/master'
mikedamay Jun 25, 2020
86f845e
Merge remote-tracking branch 'canonical/master'
mikedamay Jun 26, 2020
44c8668
Merge remote-tracking branch 'canonical/master'
mikedamay Jun 27, 2020
9bfcb00
csharp/concept-doc updated equality, tuples, randomness
mikedamay Jun 30, 2020
8f7b406
master - put aa-template in its own branch
mikedamay Jun 30, 2020
594c967
resolve merge conflicts
mikedamay Jul 1, 2020
afe157e
Merge remote-tracking branch 'canonical/master'
mikedamay Jul 1, 2020
b8aae5c
Merge remote-tracking branch 'canonical/master'
mikedamay Jul 3, 2020
3aa329a
Merge remote-tracking branch 'canonical/master'
mikedamay Jul 3, 2020
8bc85a7
Merge remote-tracking branch 'canonical/master'
mikedamay Jul 3, 2020
cc75460
resolved merge conflict
mikedamay Jul 7, 2020
ff5d9a2
csharp/structs - after grabbing structs directory from origin
mikedamay Jul 8, 2020
ae00134
master dropped files that should not have been in this branch.
mikedamay Jul 8, 2020
08814b4
Merge remote-tracking branch 'canonical/master'
mikedamay Jul 8, 2020
68ddf51
Merge remote-tracking branch 'canonical/master'
mikedamay Jul 8, 2020
754088d
Merge remote-tracking branch 'canonical/master'
mikedamay Jul 9, 2020
4971174
csharp/constants initial commit
mikedamay Jul 10, 2020
047d647
csharp/constants draft PR
mikedamay Jul 10, 2020
fec5e71
csharp/constants decapitalisd exercise name
mikedamay Jul 10, 2020
765e675
csharp/constants code style
mikedamay Jul 10, 2020
a974aec
csharp/constants - typos
mikedamay Jul 15, 2020
9562101
csharp/constants - proofing
mikedamay Jul 15, 2020
f386d81
Update languages/csharp/exercises/concept/constants/.docs/introductio…
mikedamay Jul 31, 2020
00dda9e
csharp/constants review points
mikedamay Jul 31, 2020
a3bc99b
csharp/constants review points
mikedamay Aug 1, 2020
25b650e
csharp/constants review points
mikedamay Aug 2, 2020
64c258b
csharp/constants proofing
mikedamay Aug 2, 2020
bb9f5e5
csharp/constants proofing
mikedamay Aug 2, 2020
122cfcc
csharp/constants added config.json
mikedamay Aug 6, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
126 changes: 124 additions & 2 deletions languages/csharp/config.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{
"language": "C#",
"slug": "csharp",
"active": true,
"blurb": "C# is a modern, object-oriented language with lots of great features, such as type-inference and async/await. The tooling is excellent, and there is extensive, well-written documentation.",
"version": 3,
Expand Down Expand Up @@ -42,6 +43,20 @@
"concepts": ["classes"],
"prerequisites": ["conditionals", "numbers", "strings"]
},
{
"slug": "constants",
"uuid": "54126398-4339-4073-9f5d-cf71cabcb328",
"concepts": ["constants", "defensive-copying", "readonly-collections"],
"prerequisites": [
"arrays",
"basics",
"classes",
"dictionaries",
"nested-types",
"object-initializers",
"properties"
]
},
{
"slug": "constructors",
"uuid": "655b5c37-c2eb-4da2-ab0a-3845de898835",
Expand Down Expand Up @@ -84,6 +99,26 @@
"lists"
]
},
{
"slug": "expression-bodied-members",
"uuid": "27221c37-177e-437b-b423-977e46695db9",
"concepts": [
"conditionals-ternary",
"expression-bodied-members",
"switch-expressions",
"throw-expressions"
],
"prerequisites": [
"booleans",
"classes",
"conditionals-if",
"constructors",
"datetimes",
"exceptions",
"properties",
"switch-statements"
]
},
{
"slug": "flag-enums",
"uuid": "ad240550-d732-4125-b00b-b852027a2db7",
Expand All @@ -108,6 +143,24 @@
"strings"
]
},
{
"slug": "integral-numbers",
"uuid": "86bbc073-1726-423d-b156-c394554a4a8c",
"concepts": ["casting", "integral-numbers"],
"prerequisites": ["arrays", "numbers"]
},
{
"slug": "interfaces",
"uuid": "15ac87b1-2976-4a43-a41d-1b6f6628bd9a",
"concepts": ["interfaces", "ordering"],
"prerequisites": ["classes", "lists", "properties"]
},
{
"slug": "lists",
"uuid": "5c438d02-67a6-43b6-9e3c-8ba309c4ea32",
"concepts": ["generic-types", "lists"],
"prerequisites": ["arrays", "for-loops"]
},
{
"slug": "method-overloading",
"uuid": "3f728065-98d8-472a-a84c-290f05ab1e58",
Expand All @@ -124,6 +177,24 @@
"strings"
]
},
{
"slug": "namespaces",
"uuid": "dcfd75d2-8264-4445-a95b-718867ec5eb4",
"concepts": ["accessibility", "namespaces"],
"prerequisites": ["classes", "inheritance", "nested-types"]
},
{
"slug": "nested-types",
"uuid": "694cbd8d-677d-4052-960e-69386463a94b",
"concepts": ["nested-types"],
"prerequisites": [
"classes",
"enums",
"interfaces",
"properties",
"structs"
]
},
{
"slug": "nullability",
"uuid": "370e7e54-6a96-11ea-bc55-0242ac130003",
Expand All @@ -142,6 +213,27 @@
"concepts": ["object-initializers"],
"prerequisites": ["constructors", "dictionaries", "properties"]
},
{
"slug": "operator-overloading",
"uuid": "555cf3ae-d9b0-446f-8e2d-70a81aceab49",
"concepts": ["operator-overloading"],
"prerequisites": [
"equality",
"floating-point-numbers",
"method-overloading"
]
},
{
"slug": "overflow",
"uuid": "16f760c1-29b8-4c82-a085-9b4d81002c62",
"concepts": ["overflow"],
"prerequisites": [
"exceptions",
"floating-point-numbers",
"integral-numbers",
"strings"
]
},
{
"slug": "parameters",
"uuid": "f5c259fd-a404-432f-922d-781bde34fe08",
Expand Down Expand Up @@ -172,12 +264,42 @@
"concepts": ["randomness"],
"prerequisites": ["numbers"]
},
{
"slug": "regular-expressions",
"uuid": "c63bb75d-51bd-4d5d-b37d-88e53ee0da69",
"concepts": ["regular-expressions"],
"prerequisites": [
"arrays",
"for-loops",
"strings",
"string-interpolation",
"verbatim-strings"
]
},
{
"slug": "resource-cleanup",
"uuid": "e99221eb-eecb-4746-9ef6-cc62650e41e1",
"concepts": ["resource-cleanup"],
"prerequisites": ["exceptions", "inheritance"]
},
{
"slug": "resource-lifetime",
"uuid": "1c23e163-ceca-4cce-87ec-2e016db1262b",
"concepts": ["resource-lifetime"],
"prerequisites": ["resource-cleanup"]
},
{
"slug": "string-formatting",
"uuid": "eb435600-f983-4d55-a2c8-ef8b122334a0",
"concepts": ["string-formatting", "verbatim-strings"],
"prerequisites": [
"const-readonly",
"inheritance",
"strings",
"time",
"varargs"
]
},
{
"slug": "strings",
"uuid": "eda235a5-37de-4fb6-a5c5-cf7aa2309987",
Expand All @@ -193,7 +315,7 @@
"inheritance",
"numbers",
"sets",
"signed-integers"
"integral-numbers"
]
},
{
Expand Down Expand Up @@ -229,7 +351,7 @@
"strings",
"conditionals",
"arithmetic-overflow",
"signed-integers"
"integral-numbers"
]
}
],
Expand Down
82 changes: 82 additions & 0 deletions languages/csharp/exercises/concept/constants/.docs/after.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
#### const

The [`const`][constants] modifier can be (and generally should be) applied to any field where its value is known at compile time and will not change during the lifetime of the program.

```csharp
private const int num = 1729;
public const string title = "Grand" + " Master";
```

The compiler will guide you as to what expressions can be evaluated at compile-time. Simple arithmetic operations are allowed as is string concatenation. This excludes any evaluation that would require use of the heap or stack so all method calls and references to non-primitive types are not available.

There is some discussion on the web about the performance advantages of `const` over variables. In the case of a comparison with instance non-const fields there could be a noticeable saving in memory but compared to static variables that is decidedly trivial. Any consideration of CPU performance is likely to be seen by your colleagues as [premature optimization][premature-optimization].

A more compelling reason to use `const` is that it enhances a maintainer's ability to reason about the code. Glimpsing that a field is marked as `const` or `readonly` or that a property has no setter allows the maintainer largely to dismiss it from their analysis. It is unlikely to be the seat of bugs. It is unlikely to pose difficulties in a refactoring exercise. This [Stack Overflow comment][so-consts] addresses this.

The `const` modifier can also be applied to values within methods:

```csharp
public double Area(double r)
{
const double π = 3.142;
return System.Math.Pow((π * r), 2);
}
```

Identifying a value with `const` in this way can be useful if it is used multiple times in the method or you want to draw attention to its meaning. There is no performance gain over using literals inline.

#### readonly

The [`readonly`][readonly-fields] modifier can be (and generally should be) applied to any field that cannot be made `const` where its value will not change during the lifetime of the program and is either set by an inline initializer or during instantiation (by the constructor or a method called by the constructor).

```csharp
private readonly int num;
private readonly System.Random rand = new System.Random();

public MyClass(int num)
{
this.num = num;
}
```

Use of the `readonly` modifier is encouraged for the same reasons that apply to `const`. The practice of constraining fields in this way helps maintainers reason about the code.

Note that adding the `readonly` modifier to a field prevents only the value of the field from being changed. In the case of aggregate types it does not protect the fields or properties of that type. In particular, it does not protect the contents of arrays.

```csharp
private readonly IList list = new List();

public void Foo()
{
list = new List(); // does not compile

list.Add("new stuff"); // succeeds at runtime
}
```

To ensure that all members of a reference type are protected the fields can be made `readonly` and automatic properties can be defined without a `set` accessor.

You should examine [read-only collections][readonly-collections] in the Base Class Library.

For arrays the closest you can get to a read-only version is the [`Array.AsReadOnly<T>()][as-read-only] method.

#### Defensive Copying

Reflecting back on the coding exercise, imagine you have a code-base of several hundred thousand lines. You are passed the dictionary of developers into some method you are developing. Perhaps you have been tasked with printing out details of privileged developers. You decide to blank out the eye color in the dictionary to protect the developers' privacy. Unless a [deep copy][so-deep-copy] of the dictionary was made in the `Authenticator.GetDevelopers()` method, or, even better, it was wrapped in a read-only collection then you will have just trashed the authenticator.

This follows the principle of [defensive copying][defensive-copying]. You can make sure your formal API is not circumvented by avoiding exposure to callers of internal writeable state.

#### Reference

- [Readonly fields][readonly-fields]: how to define a readonly field.
- [Constants][constants]: how to define constants.

[readonly-fields]: https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/readonly#readonly-field-example
[constants]: https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/constants
[so-consts]: https://stackoverflow.com/a/5834473/96167
[premature-optimization]: https://wiki.c2.com/?PrematureOptimization
[so-deep-copy]: https://stackoverflow.com/questions/78536/deep-cloning-objects
[defensive-copying]: https://www.informit.com/articles/article.aspx?p=31551&seqNum=2
[as-read-only]: https://docs.microsoft.com/en-us/dotnet/api/system.array.asreadonly?view=netcore-3.1
[readonly-collections]: https://docs.microsoft.com/en-us/dotnet/api/system.collections.objectmodel.readonlycollection-1?view=netcore-3.1
[so-deep-copy]: https://stackoverflow.com/questions/184710/what-is-the-difference-between-a-deep-copy-and-a-shallow-copy#:~:text=A%20deep%20copy%20occurs%20when,objects%20to%20which%20it%20refers.&text=Shallow%20copy%20is%20a%20bit,values%20in%20the%20original%20object.
30 changes: 30 additions & 0 deletions languages/csharp/exercises/concept/constants/.docs/hints.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
## General

- [Readonly fields][readonly-fields]: how to define a readonly field.
- [Constants][constants]: how to define constants.

## 1. Set appropriate fields and properties to const

- Constants in C# are discussed [here][constants].

## 2. Set appropriate fields to readonly

- Read-only fields are discussed [here][readonly-fields].

## 3. Remove set accessor or make it private for any appropriate field

- This [article][properties] discusses C# properties.

## 4. Ensure that the admin cannot be tampered with

- See [this][defensive-copying] discussion on the pattern and purpose of defensive copying.

## 5. Ensure that the developers cannot be tampered with

- Read-only dictionaries are discussed [here][readonly-dictionaries].

[readonly-fields]: https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/readonly#readonly-field-example
[constants]: https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/constants
[properties]: https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/properties
[defensive-copying]: https://www.informit.com/articles/article.aspx?p=31551&seqNum=2
[readonly-dictionaries]: https://docs.microsoft.com/en-us/dotnet/api/system.collections.objectmodel.readonlydictionary-2?view=netcore-3.1
21 changes: 21 additions & 0 deletions languages/csharp/exercises/concept/constants/.docs/instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
The authentication system that you last saw in (TODO cross-ref-tba) is in need of some attention. You have been tasked with cleaning up the code. Such a cleanup project will not only make life easy for future maintainers but will expose and fix some security vulnerabilities.

## 1. Set appropriate fields and properties to const
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a bit of a technical description, but I must admit that I could not come up with a nice name either. Maybe something like "Ensure that constant values cannot be tampered with"

Copy link
Contributor Author

@mikedamay mikedamay Aug 1, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that making variables readonly is consistent with your wording which itself mentions constants. I think the test here is to find all the appropriate candidates. I am taking the view that the syntax is trivial.


This is a refactoring task. Add the `const` modifier to any members of `Authenticator` or `Identity` that you think appropriate.

## 2. Set appropriate fields to readonly

This is a refactoring task. Add the `readonly` modifier to any fields of the `Authenticator` class or the `Identity` struct that you think appropriate.

## 3. Ensure that the class cannot be changed once it has been created

Remove the `set` accessor or make it `private` for any appropriate property on the `Authenticator` class or `Identity` struct.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the example, I don't see this being applied to any properties. Am I mis-reading the code?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right. The students would not have responded well to a "trick" question. Changed GetAdmin() to a property and changed task 3 to require immutability. It wouldn't be a bad place to introduce the concept except that we already have 3 concepts.


## 4. Ensure that the admin cannot be tampered with

At present the admin identity field is returned by a call to `Admin`. This is not ideal as the caller can modify the field. Find a way to prevent the caller modifying the details of admin on the `Authenticator` object.

## 5. Ensure that the developers cannot be tampered with

At present the dictionary containing the hard coded privileged developer identities is returned by a call to `GetDevelopers()`. This is not ideal as the caller can modify the dictionary. Find a way to prevent the caller modifying the details of admin on the `Authenticator` object.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will students know about ReadOnlyDictionary? If not, this might be something that we could introduce in this exercise too to make the exercise a bit more interesting.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

43 changes: 43 additions & 0 deletions languages/csharp/exercises/concept/constants/.docs/introduction.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
The `const` modifier can be (and generally should be) applied to any field where its value is known at compile time and will not change during the lifetime of the program.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like how briefly you've described it. The const modifier can also be applied to variables, but I assume you'll add that to the after.md file (which makes sense).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done


```csharp
private const int num = 1729;
public const string title = "Grand" + " Master";
```

The `readonly` modifier can be (and generally should be) applied to any field that cannot be made `const` where its value will not change during the lifetime of the program and is either set by an inline initializer or during instantiation (by the constructor or a method called by the constructor).

```csharp
private readonly int num;
private readonly System.Random rand = new System.Random();

public MyClass(int num)
{
this.num = num;
}
```

#### Read-only collections

Although the `readonly` modifier prevents the value or reference in a field from being overwritten, the modifier provides no protection for the members of a reference type.

```csharp
readonly List<int> ints = new List<int>();

void Foo()
{
ints.Add(1); // ok
ints = new List<int>(); // fails to compile
}
```

To ensure that all members of a reference type are protected the fields can be made `readonly` and automatic properties can be defined without a `set` accessor.

The Base Class Library (BCL) provides some readonly versions of collections where there is a requirement to stop members of a collections being updated. These come in the form of wrappers:

- `ReadOnlyDictionary<T>` exposes a `Dictionary<T>` as read-only.
- `ReadOnlyCollection<T>` exposes a `List<T>` as read-only.

#### Defensive copying

In security sensitive situations (or even simply on a large code-base where developers have different priorities and agendas) you should avoid allowing a class's public API to be circumvented by accepting and storing a method's mutable parameters or by exposing a mutable member of a class through a return value or as an `out` parameter.
Loading