From 4069cca97782d307c4ebcc2419cc0c915f3ce1d2 Mon Sep 17 00:00:00 2001 From: Erik Schierboom Date: Mon, 31 Oct 2022 08:34:15 +0100 Subject: [PATCH] two-fer: add dig deeper content (#2010) --- .../practice/two-fer/.approaches/config.json | 21 ++ .../two-fer/.approaches/introduction.md | 51 +++++ .../.approaches/method-overloading/content.md | 73 +++++++ .../method-overloading/snippet.txt | 8 + .../.approaches/optional-parameter/content.md | 53 +++++ .../optional-parameter/snippet.txt | 4 + .../practice/two-fer/.articles/config.json | 18 ++ .../content.md | 181 ++++++++++++++++++ .../snippet.md | 10 + .../.articles/string-formatting/content.md | 53 +++++ .../.articles/string-formatting/snippet.md | 6 + 11 files changed, 478 insertions(+) create mode 100644 exercises/practice/two-fer/.approaches/config.json create mode 100644 exercises/practice/two-fer/.approaches/introduction.md create mode 100644 exercises/practice/two-fer/.approaches/method-overloading/content.md create mode 100644 exercises/practice/two-fer/.approaches/method-overloading/snippet.txt create mode 100644 exercises/practice/two-fer/.approaches/optional-parameter/content.md create mode 100644 exercises/practice/two-fer/.approaches/optional-parameter/snippet.txt create mode 100644 exercises/practice/two-fer/.articles/config.json create mode 100644 exercises/practice/two-fer/.articles/optional-parameters-vs-method-overloading/content.md create mode 100644 exercises/practice/two-fer/.articles/optional-parameters-vs-method-overloading/snippet.md create mode 100644 exercises/practice/two-fer/.articles/string-formatting/content.md create mode 100644 exercises/practice/two-fer/.articles/string-formatting/snippet.md diff --git a/exercises/practice/two-fer/.approaches/config.json b/exercises/practice/two-fer/.approaches/config.json new file mode 100644 index 0000000000..64e375dafc --- /dev/null +++ b/exercises/practice/two-fer/.approaches/config.json @@ -0,0 +1,21 @@ +{ + "introduction": { + "authors": ["erikschierboom"] + }, + "approaches": [ + { + "uuid": "f567dfff-0f68-4226-b924-c6baf19babbf", + "slug": "optional-parameter", + "title": "Optional parameter", + "blurb": "Use an optional parameter and its default value.", + "authors": ["erikschierboom"] + }, + { + "uuid": "e3999368-f13c-42e9-a814-e385356fd640", + "slug": "method-overloading", + "title": "Method overloading", + "blurb": "Use method overloading.", + "authors": ["erikschierboom"] + } + ] +} diff --git a/exercises/practice/two-fer/.approaches/introduction.md b/exercises/practice/two-fer/.approaches/introduction.md new file mode 100644 index 0000000000..a7e9d22f22 --- /dev/null +++ b/exercises/practice/two-fer/.approaches/introduction.md @@ -0,0 +1,51 @@ +# Introduction + +The key to this exercise is to allow the `Speak()` method to be called _with_ and _without_ an argument. +There are two ways to do this in C#: + +1. [Optional parameters][optional-parameters] +2. [Method overloading][method-overloading] + +## General guidance + +- Try to not repeat any string building logic ([DRY][dry]) +- [String interpolation][article-string-formatting] is a great way to build strings + +## Approach: optional parameter + +```csharp +public static string Speak(string name = "you") +{ + return $"One for {name}, one for me."; +} +``` + +For more information, check the [optional-parameter approach][approach-optional-parameter]. + +## Approach: method overloading + +```csharp +public static string Speak() +{ + return Speak("you"); +} + +public static string Speak(string name) +{ + return $"One for {name}, one for me."; +} +``` + +For more information, check the [method overloading][approach-method-overloading]. + +## Which approach to use? + +The optional parameter approach is not only more concise, it also more clearly encodes that name has a default value (which is not immediately apparent with the method overloading approach). + +[optional-parameters]: https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/named-and-optional-arguments +[method-overloading]: https://www.pluralsight.com/guides/overload-methods-invoking-overload-methods-csharp +[approach-optional-parameter]: https://exercism.org/tracks/csharp/exercises/two-fer/approaches/optional-parameter +[approach-method-overloading]: https://exercism.org/tracks/csharp/exercises/two-fer/approaches/method-overloading +[article-optional-parameters-vs-method-overloading]: https://exercism.org/tracks/csharp/exercises/two-fer/articles/optional-parameters-vs-method-overloading +[article-string-formatting]: https://exercism.org/tracks/csharp/exercises/two-fer/articles/string-formatting +[dry]: https://en.wikipedia.org/wiki/Don%27t_repeat_yourself diff --git a/exercises/practice/two-fer/.approaches/method-overloading/content.md b/exercises/practice/two-fer/.approaches/method-overloading/content.md new file mode 100644 index 0000000000..93b41deae6 --- /dev/null +++ b/exercises/practice/two-fer/.approaches/method-overloading/content.md @@ -0,0 +1,73 @@ +# Method overloading + +```csharp +public static class TwoFer +{ + public static string Speak() + { + return Speak("you"); + } + + public static string Speak(string name) + { + return $"One for {name}, one for me."; + } +} +``` + +First, let's define the `Speak()` method that has a single parameter for the name: + +```csharp +public static string Speak(string name) +{ + return $"One for {name}, one for me."; +} +``` + +Within the method, we use [string interpolation][string-interpolation] to build the return string where `{name}` is replaced with the value of the `name` parameter. + +Then we define a second, parameterless method that calls the other `Speak()` method with `"you"` as its argument: + +```csharp +public static string Speak() +{ + return Speak("you"); +} +``` + +The compiler will be able to figure out which method to call based on the number of arguments passed to the `Speak()` method. + +## Shortening + +We can shorten the methods by rewriting them as [expression-bodied methods][expression-bodied-method]: + +```csharp +public static string Speak() => + Speak("you"); + +public static string Speak(string name) => + $"One for {name}, one for me."; +``` + +or + +```csharp +public static string Speak() => Speak("you"); + +public static string Speak(string name) => $"One for {name}, one for me."; +``` + +## String formatting + +The [string formatting article][article-string-formatting] discusses alternative ways to format the returned string. + +## Optional parameters vs. method overloads + +The main alternative to using method overloads is to use an [optional parameter approach][approach-optional-parameter]. If you're interested in how these two solutions compare to each other, go check out our [optional parameters vs method overloads article][article-optional-parameters-vs-method-overloading]. + +[approach-optional-parameter]: https://exercism.org/tracks/csharp/exercises/two-fer/approaches/optional-parameter +[article-optional-parameters-vs-method-overloading]: https://exercism.org/tracks/csharp/exercises/two-fer/articles/optional-parameters-vs-method-overloading +[article-string-formatting]: https://exercism.org/tracks/csharp/exercises/two-fer/articles/string-formatting +[optional-parameters-introduction]: https://learn.microsoft.com/en-us/archive/msdn-magazine/2010/july/csharp-4-0-new-csharp-features-in-the-net-framework-4#named-arguments-and-optional-parameters +[method-overloading]: https://www.pluralsight.com/guides/overload-methods-invoking-overload-methods-csharp +[string-interpolation]: https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/tokens/interpolated diff --git a/exercises/practice/two-fer/.approaches/method-overloading/snippet.txt b/exercises/practice/two-fer/.approaches/method-overloading/snippet.txt new file mode 100644 index 0000000000..af7dc00755 --- /dev/null +++ b/exercises/practice/two-fer/.approaches/method-overloading/snippet.txt @@ -0,0 +1,8 @@ +public static string Speak() +{ + return Speak("you"); +} +public static string Speak(string name) +{ + return $"One for {name}, one for me."; +} diff --git a/exercises/practice/two-fer/.approaches/optional-parameter/content.md b/exercises/practice/two-fer/.approaches/optional-parameter/content.md new file mode 100644 index 0000000000..9f51450d7b --- /dev/null +++ b/exercises/practice/two-fer/.approaches/optional-parameter/content.md @@ -0,0 +1,53 @@ +# Optional parameter + +```csharp +public static class TwoFer +{ + public static string Speak(string name = "you") + { + return $"One for {name}, one for me."; + } +} +``` + +We define a single `Speak()` method with one optional parameter: `name`. +This optional parameter has its default value set to the string `"you"`. +The caller of a method with an optional parameter can either pass in a value (which is then used) or not pass in a value (which caused the default value to be used). + +These calls are thus identical: + +```csharp +`Speak()` +`Speak("you")` +``` + +Within the method, we use [string interpolation][string-interpolation] to build the return string where `{name}` is replaced with the value of the `name` parameter. + +## Shortening + +We can shorten the method by rewriting it as an [expression-bodied method][expression-bodied-method]: + +```csharp +public static string Speak(string name = "you") => + $"One for {name}, one for me."; +``` + +or + +```csharp +public static string Speak(string name = "you") => $"One for {name}, one for me."; +``` + +## String formatting + +The [string formatting article][article-string-formatting] discusses alternative ways to format the returned string. + +## Optional parameters vs. method overloads + +The main alternative to using an optional parameter is to use a [method overloading approach][approach-method-overloading]. If you're interested in how these two solutions compare to each other, go check out our [optional parameters vs method overloads article][article-optional-parameters-vs-method-overloading]. + +[string-interpolation]: https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/tokens/interpolated +[expression-bodied-method]: https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/statements-expressions-operators/expression-bodied-members#methods +[approach-method-overloading]: https://exercism.org/tracks/csharp/exercises/two-fer/approaches/method-overloading +[article-optional-parameters-vs-method-overloading]: https://exercism.org/tracks/csharp/exercises/two-fer/articles/optional-parameters-vs-method-overloading +[article-string-formatting]: https://exercism.org/tracks/csharp/exercises/two-fer/articles/string-formatting diff --git a/exercises/practice/two-fer/.approaches/optional-parameter/snippet.txt b/exercises/practice/two-fer/.approaches/optional-parameter/snippet.txt new file mode 100644 index 0000000000..c5eb45bda0 --- /dev/null +++ b/exercises/practice/two-fer/.approaches/optional-parameter/snippet.txt @@ -0,0 +1,4 @@ +public static string Speak(string name = "you") +{ + return $"One for {name}, one for me."; +} \ No newline at end of file diff --git a/exercises/practice/two-fer/.articles/config.json b/exercises/practice/two-fer/.articles/config.json new file mode 100644 index 0000000000..4cfa0fb204 --- /dev/null +++ b/exercises/practice/two-fer/.articles/config.json @@ -0,0 +1,18 @@ +{ + "articles": [ + { + "uuid": "9c9b759e-43c8-46ea-893d-59c310b84e21", + "slug": "optional-parameters-vs-method-overloading", + "title": "Optional parameters vs. method overloading", + "blurb": "Find out how optional parameters compare to method overloading.", + "authors": ["erikschierboom"] + }, + { + "uuid": "bc651e10-b512-442c-a568-559867d31c91", + "slug": "string-formatting", + "title": "Formatting the return string", + "blurb": "Show the different ways in which the return string can be formatted.", + "authors": ["erikschierboom"] + } + ] +} diff --git a/exercises/practice/two-fer/.articles/optional-parameters-vs-method-overloading/content.md b/exercises/practice/two-fer/.articles/optional-parameters-vs-method-overloading/content.md new file mode 100644 index 0000000000..971426a802 --- /dev/null +++ b/exercises/practice/two-fer/.articles/optional-parameters-vs-method-overloading/content.md @@ -0,0 +1,181 @@ +# Optional parameters vs. method overloading + +## Method overloading + +Prior to C# 4.0, if you wanted a method to have a default value for one of its parameters, you needed to define an [overload of that method][method-overloading] (which the [method overloading approach][approach-method-overloading] uses): + +```csharp +public static string Speak() +{ + return Speak("you"); +} + +public static string Speak(string name) +{ + return $"One for {name}, one for me."; +} +``` + +These two methods have the same name, but they have a different number of parameters. +This allows the compiler to distinguish between calls to these methods based on the number of arguments passed to the method. + +## Optional parameters + +C# 4.0 introduced [optional parameters][optional-parameters] (which the [optional parameter approach][approach-optional-parameter] uses): + +```csharp +public static string Speak(string name = "you") +{ + return $"One for {name}, one for me."; +} +``` + +Using an optional parameter, we can assign a default value to that parameter which is then used if no value is passed for that parameter: `Speak()` and `Speak("you")` are equivalent. +This new syntax allowed for a more succinct way of defining default values for parameters. + +```exercism/note +Method overloading can do everything optional parameters can do, but the latter are arguably easier to read and write. +``` + +## What are the differences? + +The main difference between the two is that optional parameters are more concise. +But what about their implementation? +Are optional parameters just overloaded methods in disguise? +Well, let's find out! + +The most fool-proof way to check this is by examining the IL code (which is the intermediate code the .NET runtime executes) that both types of syntax compile to. +A great way to check the generated IL code for a bit of C# code is by using [sharplab.io]. +We created a [sharplab gist][il-comparison] for the following C# code: + +```csharp +public static class TwoFer +{ + public static string SpeakOverload() + { + return SpeakOverload("you"); + } + + public static string SpeakOverload(string name) + { + return $"One for {name}, one for me."; + } + + public static string SpeakOptional(string name = "you") + { + return $"One for {name}, one for me."; + } +} +``` + +We can then use the IL output option to view the generated IL code. + +### IL code: method overloading + +This is the IL code for the two method overloading methods: + +``` +.method public hidebysig static + string SpeakOverload () cil managed +{ + // Method begins at RVA 0x2094 + // Code size 16 (0x10) + .maxstack 1 + .locals init ( + [0] string + ) + + IL_0000: nop + IL_0001: ldstr "you" + IL_0006: call string TwoFer::SpeakOverload(string) + IL_000b: stloc.0 + IL_000c: br.s IL_000e + + IL_000e: ldloc.0 + IL_000f: ret +} // end of method TwoFer::SpeakOverload + +.method public hidebysig static + string SpeakOverload ( + string name + ) cil managed +{ + // Method begins at RVA 0x20b0 + // Code size 22 (0x16) + .maxstack 3 + .locals init ( + [0] string + ) + + IL_0000: nop + IL_0001: ldstr "One for " + IL_0006: ldarg.0 + IL_0007: ldstr ", one for me." + IL_000c: call string [System.Runtime]System.String::Concat(string, string, string) + IL_0011: stloc.0 + IL_0012: br.s IL_0014 + + IL_0014: ldloc.0 + IL_0015: ret +} // end of method TwoFer::SpeakOverload +``` + +### IL code: optional parameter + +This is the IL code for the optional parameter method: + +``` +.method public hidebysig static + string SpeakOptional ( + [opt] string name + ) cil managed +{ + .param [1] = "you" + // Method begins at RVA 0x20d4 + // Code size 22 (0x16) + .maxstack 3 + .locals init ( + [0] string + ) + + IL_0000: nop + IL_0001: ldstr "One for " + IL_0006: ldarg.0 + IL_0007: ldstr ", one for me." + IL_000c: call string [System.Runtime]System.String::Concat(string, string, string) + IL_0011: stloc.0 + IL_0012: br.s IL_0014 + + IL_0014: ldloc.0 + IL_0015: ret +} // end of method TwoFer::SpeakOptional +``` + +### Comparison + +The big differences are that the generated IL code: + +- does not include code that resembles the parameterless overloaded method's IL code, +- is _nearly_ identical to the single parameter, overloaded method's IL code, except for this one line: + +``` +.param [1] = "you" +``` + +This demonstrates that optional parameters are _not_ overloaded methods in disguise. + +```exercism/note +Note how the interpolated string is actually converted to a `string.Concat` call in the IL code. +``` + +## Conclusion + +Optional parameters are not only more elegant to use, they also result in a slightly smaller binary as less code is generated. + +[approaches]: https://exercism.org/tracks/csharp/exercises/two-fer/approaches +[approach-optional-parameter]: https://exercism.org/tracks/csharp/exercises/two-fer/approaches/optional-parameter +[approach-method-overloading]: https://exercism.org/tracks/csharp/exercises/two-fer/approaches/method-overloading +[optional-parameters]: https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/named-and-optional-arguments +[method-overloading]: https://www.pluralsight.com/guides/overload-methods-invoking-overload-methods-csharp +[sharplab.io]: https://sharplab.io +[il-comparison]: https://sharplab.io/#gist:a8991b6f70ee94145dbd43b571b9ef61 diff --git a/exercises/practice/two-fer/.articles/optional-parameters-vs-method-overloading/snippet.md b/exercises/practice/two-fer/.articles/optional-parameters-vs-method-overloading/snippet.md new file mode 100644 index 0000000000..5829e5666e --- /dev/null +++ b/exercises/practice/two-fer/.articles/optional-parameters-vs-method-overloading/snippet.md @@ -0,0 +1,10 @@ +```csharp +public static string SpeakOptional(string name = "you") => + $"One for {name}, one for me."; + +public static string SpeakOverload() => + return Speak("you"); + +public static string SpeakOverload(string name) => + return $"One for {name}, one for me."; +``` diff --git a/exercises/practice/two-fer/.articles/string-formatting/content.md b/exercises/practice/two-fer/.articles/string-formatting/content.md new file mode 100644 index 0000000000..800f526331 --- /dev/null +++ b/exercises/practice/two-fer/.articles/string-formatting/content.md @@ -0,0 +1,53 @@ +# String formatting + +There are various ways in which you can format the return string. + +## Option 1: string interpolation + +[String interpolation][string-interpolation] was introduced in C# 6.0 and is the most idiomatic way to build up a string with one more variable parts. + +```csharp +$"One for {name}, one for me."; +``` + +## Option 2: string concatenation + +As there are few variable parts in the returned string (just one), regular [string concatentation][string-concatenation] works well too: + +```csharp +"One for " + name + ", one for me."; +``` + +It is slightly more verbose than string interpolation, but still completely reasonable. + +## Option 3: using `string.Format()` + +Before string interpolation was introduced in C# 6, [`string.Format()`][string-format] was the go-to option for dynamically formatting strings. + +```csharp +string.Format("One for {0}, one for me.", name); +``` + +String interpolation is in most ways superior to `string.Format()`, so it is no longer idiomatic to use `string.Format()`. + +## Option 4: using `string.Concat()` + +Another option is [`string.Concat()`][string-concat]: + +```csharp +string.Concat("One for ", name, ", one for me."); +``` + +## Conclusion + +String interpolation is the preferred and idiomatic way to format strings. +String concatentation is absolutely a viable option too, as there is only one variable part. +Both `string.Format()` and `string.Concat()` are functionally equivalent, but more cumbersome to read and there we don't recommend using them. + +[approaches]: https://exercism.org/tracks/csharp/exercises/two-fer/approaches +[approach-optional-parameter]: https://exercism.org/tracks/csharp/exercises/two-fer/approaches/optional-parameter +[approach-method-overloading]: https://exercism.org/tracks/csharp/exercises/two-fer/approaches/method-overloading +[string-interpolation]: https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/tokens/interpolated +[string-format]: https://learn.microsoft.com/en-us/dotnet/api/system.string.format#Starting +[string-concatenation]: https://learn.microsoft.com/en-us/dotnet/csharp/how-to/concatenate-multiple-strings#-and--operators +[string-concat]: https://learn.microsoft.com/en-us/dotnet/api/system.string.concat diff --git a/exercises/practice/two-fer/.articles/string-formatting/snippet.md b/exercises/practice/two-fer/.articles/string-formatting/snippet.md new file mode 100644 index 0000000000..2f81b0b2cc --- /dev/null +++ b/exercises/practice/two-fer/.articles/string-formatting/snippet.md @@ -0,0 +1,6 @@ +```csharp +$"One for {name}, one for me."; +"One for " + name + ", one for me."; +string.Format("One for {0}, one for me.", name); +string.Concat("One for ", name, ", one for me."); +```