Visit the bob exercise on Exercism to read the full instructions and download the exercise files.
Dig Deeper
If
if statements
using System.Linq;
public static class Bob
{
public static string Response(string message)
{
if (IsSilence(message))
return "Fine. Be that way!";
if (IsYell(message) && IsQuestion(message))
return "Calm down, I know what I'm doing!";
if (IsYell(message))
return "Whoa, chill out!";
if (IsQuestion(message))
return "Sure.";
return "Whatever.";
}
private static bool IsSilence(string message)
{
return string.IsNullOrWhiteSpace(message);
}
private static bool IsYell(string message)
{
return message.Any(char.IsLetter) && message.ToUpperInvariant() == message;
}
private static bool IsQuestion(string message)
{
return message.TrimEnd().EndsWith("?");
}
}
In this approach you have a series of if statements using the private methods to evaluate the conditions.
As soon as the right condition is found, the correct response is returned.
Note that there are no else if or else statements.
If an if statement can return, then an else if or else is not needed.
Execution will either return or will continue to the next statement anyway.
The LINQ method EndsWith is used to determine if the input ends with a question mark.
The String method IsNullOrWhiteSpace is used to determine if the input is null or only contains whitespace characters.
The first half of the yell condition
message.Any(char.IsLetter) && message.ToUpperInvariant()
is constructed from the Any LINQ method and the IsLetter Char method to ensure there is at least one letter character in the String.
This is because the second half of the condition tests that the uppercased input is the same as the input.
If the input were only "123" it would equal itself uppercased, but without letters it would not be a yell.
The uppercasing is done by using the String method ToUpperInvariant.
The invariant culture represents a culture that is culture-insensitive.
It is associated with the English language but not with a specific country or region.
For more information, see the [CultureInfo.InvariantCulture](https://learn.microsoft.com/en-us/dotnet/api/system.globalization.cultureinfo.invariantculture) property.
Extension methods
For this exercise you could add behavior to the String type using Extension methods, like so
private static bool IsSilence(this string message) =>
string.IsNullOrWhiteSpace(message);
which would be called like so
if (message.IsSilence())
return "Fine. Be that way!";
Shortening
When the body of an if statement is a single line, both the test expression and the body could be put on the same line, like so
if (IsSilence(message)) return "Fine. Be that way!";
The C# Coding Conventions advise to write only one statement per line in the layout conventions section,
but the conventions begin by saying you can use them or adapt them to your needs.
Your team may choose to overrule them.
When the body of a function is a single expression, the function can be implemented as a member-bodied expression, like so
private static bool IsSilence(string message) =>
string.IsNullOrWhiteSpace(message);
or
private static bool IsSilence(string message) => string.IsNullOrWhiteSpace(message);
A ternary operator can be used to shorten the code and make the logic more efficient, like so
if (IsSilence(message))
return "Fine. Be that way!";
if (IsQuestion(message))
return IsYell(message) ? "Calm down, I know what I'm doing!": "Sure.";
if (IsYell(message))
return "Whoa, chill out!";
return "Whatever.";
If the input is a question, then the the ternary operator returns the response for a yelled question or just a question.
If the input is not a question, then the function tests if it is a yell.
If the input is a yell, then it can simply return the response for a yell, since it is already known that it is not a question.
The ternary operator makes the code more efficient while reducing the number of lines.
Switch on a tuple
switch on a tuple
using System.Linq;
public static class Bob
{
private static bool IsShout(string input) => input.Any(c => char.IsLetter(c)) && input.ToUpper() == input;
public static string Response(string statement)
{
var input = statement.TrimEnd();
if (input == "")
return "Fine. Be that way!";
switch ((input.EndsWith('?'), IsShout(input)))
{
case (true, true): return "Calm down, I know what I'm doing!";
case (_, true): return "Whoa, chill out!";
case (true, _): return "Sure.";
default: return "Whatever.";
}
}
}
In this approach you use a switch to test if there is a question or a shout.
The switch returns the right response for a question, shout, shouted question, or for not being a shout or question.
The String TrimEnd method is applied to the input to eliminate any whitespace at the end of the input.
If the string has no characters left, it returns the response for saying nothing.
Note that a `null` `string` would be different from a `string` of all whitespace.
A `null` `string` would throw an `Exception` if `TrimEnd` were applied to it.
To test a `string` that might be `null` or only whitespace, the [IsNullOrWhiteSpace](https://learn.microsoft.com/en-us/dotnet/api/system.string.isnullorwhitespace) method of `String` would be used.
Next a tuple is made from the conditions for a queston and a shout.
The first half of the shout condition
input.Any(c => char.IsLetter(c)) && input.ToUpper() == input
is constructed from the Any LINQ method and the IsLetter Char method to ensure there is at least one letter character in the String.
This is because the second half of the condition tests that the uppercased input is the same as the input.
If the input were only "123" it would equal itself uppercased, but without letters it would not be a shout.
The tuple is tested in a switch.
It checks the values of the expressions in the tuple, and uses the _ discard pattern to disregard a value.
If none of the patterns match, the default arm of the switch returns the response when the input is neither a question nor a shout.
Shortening
When the body of an if statement is a single line, both the test expression and the body could be put on the same line, like so
if (input == "") return "Fine. Be that way!";
The C# Coding Conventions advise to write only one statement per line in the layout conventions section,
but the conventions begin by saying you can use them or adapt them to your needs.
Your team may choose to overrule them.
For Any, the lambda expression of c => char.IsLetter(c) can be shortened to just the method call of char.IsLetter like so
input.Any(char.IsLetter)
Any passes a single character in each iteration, and the char.IsLetter method is called on that character implicitly.
There is a detailed description of how it works in the accepted answer for this StackOverflow question.
Answer array
Answer array
using System.Linq;
public static class Bob
{
private static readonly string[] answers = {"Whatever.", "Sure.", "Whoa, chill out!", "Calm down, I know what I'm doing!"};
public static string Response(string statement)
{
var input = statement.TrimEnd();
if (input == "")
return "Fine. Be that way!";
var isShout = input.Any(c => char.IsLetter(c)) && input.ToUpper() == input ? 2: 0;
var isQuestion = input.EndsWith('?') ? 1: 0;
return answers[isShout + isQuestion];
}
}
In this approach you define an array that contains Bob’s answers, and each condition is given a score.
The correct answer is selected from the array by using the score as the array index.
The String TrimEnd method is applied to the input to eliminate any whitespace at the end of the input.
If the string has no characters left, it returns the response for saying nothing.
Note that a `null` `string` would be different from a `string` of all whitespace.
A `null` `string` would throw an `Exception` if `TrimEnd` were applied to it.
To test a string that might be `null` or only whitespace, the [IsNullOrWhiteSpace](https://learn.microsoft.com/en-us/dotnet/api/system.string.isnullorwhitespace) method of `String` would be used.
The first half of the shout condition
input.Any(c => char.IsLetter(c)) && input.ToUpper() == input
is constructed from the Any LINQ method and the IsLetter Char method to ensure there is at least one letter character in the String.
This is because the second half of the condition tests that the uppercased input is the same as the input.
If the input were only "123" it would equal itself uppercased, but without letters it would not be a shout.
The conditions of being a question and being a shout are assigned scores through the use of the ternary operator.
For example, giving a question a score of 1 would use an index of 1 to get the element from the answers array, which is "Sure.".
| isShout | isQuestion | Index | Answer |
|---|
false | false | 0 + 0 = 0 | "Whatever." |
false | true | 0 + 1 = 1 | "Sure." |
true | false | 2 + 0 = 2 | "Whoa, chill out!" |
true | true | 2 + 1 = 3 | "Calm down, I know what I'm doing!" |
Shortening
When the body of an if statement is a single line, both the test expression and the body could be put on the same line, like so
if (input == "") return "Fine. Be that way!";
The C# Coding Conventions advise to write only one statement per line in the layout conventions section,
but the conventions begin by saying you can use them or adapt them to your needs.
Your team may choose to overrule them.
For Any, the lambda expression of c => char.IsLetter(c) can be shortened to just the method call of char.IsLetter like so
input.Any(char.IsLetter)
Any passes a single character in each iteration, and the char.IsLetter method is called on that character implicitly.
There is a detailed description of how it works in the accepted answer for this StackOverflow question.
Source: Exercism csharp/bob