switch expression
Previously, we learned about the switch case structure, which helps us assign or execute specific logic based on the evaluated comparison result.
Dart 3 introduced the switch expression, which is an improvement over the classic switch case. This enhanced version allows using the switch as an expression, which means it can produce a value that can be assigned to a variable or used wherever an expression is expected.
Let's see a simple example. Assuming that Monday is the first day of the week and represented by the number one, Tuesday is the second day, and so on until Sunday. Given an integer, we want to print the corresponding day. Using the classic switch case, the code would look like this:
// Example using the classic switch case
void main() {
final currentDay = getDay(5);
print('Today is $currentDay'); // Today is Friday
}
String getDay(int day) {
switch (day) {
case 1:
return 'Monday';
case 2:
return 'Tuesday';
case 3:
return 'Wednesday';
case 4:
return 'Thursday';
case 5:
return 'Friday';
case 6:
return 'Saturday';
case 7:
return 'Sunday';
default:
return 'The day does not exist';
}
}
In the above code, we defined multiple cases with all possible values for the days of the week, and we included a default case for values that do not match any of the days of the week.
Using the switch expression, we can make the code more concise and easier to read:
// Example using the new switch expression
void main() {
final currentDay = getDay(5);
print('Today is $currentDay'); // Today is Friday
}
String getDay(int day) {
return switch (day) {
1 => 'Monday',
2 => 'Tuesday',
3 => 'Wednesday',
4 => 'Thursday',
5 => 'Friday',
6 => 'Saturday',
7 => 'Sunday',
_ => 'The day does not exist',
};
}
In this example, the day
expression is evaluated and compared to different cases using the =>
syntax.
If the value of day
is 1, the result is 'Monday'
. The underscore _
is used as a default case for any other
value that does not match the previous cases.
Pattern Matchingβ
Pattern matching is a technique that allows us to check if a value matches a specific pattern and perform actions based on it. It is used to make more complex and flexible comparisons than simple equalities or inequalities.
If we want to determine whether a day is a "weekday" or a "weekend," we can write the following code:
void main() {
final currentDay = getDay(4);
print('Today is $currentDay'); // Weekday
}
String getDay(int day) {
return switch (day) {
>= 1 && <= 5 => 'Weekday',
6 || 7 => 'Weekend',
_ => 'The day does not exist',
};
}
We can also assign the value produced by the switch expression to a variable. The modified code would look like this:
void main() {
final day = 5;
final currentDay = switch (day) {
>= 1 && <= 5 => 'Weekday',
6 || 7 => 'Weekend',
_ => 'The day does not exist',
};
print('Today is $currentDay'); // Weekday
}
Sealed Classesβ
Sealed classes in Dart are often used in combination with switch expressions. The switch structure can exhaustively handle the subclasses of a sealed class, ensuring that all possible cases are handled.
void main() {
print('The area of the circle is: ${calculateArea(Circle(5))}');
print('The area of the square is: ${calculateArea(Square(10))}');
}
double calculateArea(Shape shape) {
return switch (shape) {
Square(side: final side) => side * side,
Circle(radius: final radius) => 3.14 * radius * radius,
};
}
sealed class Shape {}
class Square extends Shape {
Square(this.side);
final double side;
}
class Circle extends Shape {
Circle(this.radius);
final double radius;
}
The above example works because the Shape
class is sealed. If it were not, we would
encounter a non_exhaustive_switch_expression error.
Guard Clausesβ
We can use the keyword when in conjunction with switch to specify a guard clause. For example, if we want to deserialize JSON:
void main() {
var animal1Json = {
'name': 'Max',
'type': 'Cat',
};
var animal2Json = {
'name': 'Coco',
'type': 'Dog',
};
var anotherJson = {
'name': 'Yayo',
'age': '23',
};
print(parseJson(animal1Json)); // The cat is: Max
print(parseJson(animal2Json)); // The dog is: Coco
print(parseJson(anotherJson)); // Exception: Error while deserializing JSON
}
Animal parseJson(Map<String, dynamic> map) {
return switch (map) {
{
'name': String name,
'type': String type,
}
when type == 'Cat' => Cat(name),
{
'name': String name,
'type': String type,
}
when type == 'Dog' => Dog(name),
_ => throw Exception('Error while deserializing JSON')
};
}
class Animal {
Animal(this.name);
final String name;
}
class Cat extends Animal {
Cat(String name) : super(name);
String toString() => 'The cat is: $name';
}
class Dog extends Animal {
Dog(String name) : super(name);
String toString() => 'The dog is: $name';
}
In the above code snippet, we have three JSON objects: one representing a dog, another representing a cat, and the
last one having no type. In the switch, we use the when type == 'Dog'
or when type == 'Cat'
to evaluate if the
JSON represents a dog or cat and create an instance of the respective Dog
or Cat
class. If the JSON has an
unsupported format, we throw an exception.
Conclusionβ
In conclusion, switch expressions in Dart 3 significantly improve over traditional switch case structures. This new version allows using the switch as an expression, making the code more concise and readable.
It also introduces the ability to perform pattern matching, providing greater comparison flexibility. Additionally, we can use sealed classes in combination with switch, ensuring that all possible cases are handled.
Finally, guard clauses, using the keyword when, allow us to perform additional checks for JSON deserialization or handle special cases.
Overall, these enhancements to switch expressions in Dart 3 make it easier to develop more concise and robust applications.