从零开始独立游戏开发学习笔记(十七)--Unity学习笔记(

好了,忙的时候结束了。

继续讲述和对象相关的知识。这一章讲使用模式匹配进行类型转换。

  1. 如何安全地格式转换(模式匹配)

由于对象具有多态性。一个具有基类类型的变量是可以存放 derived 类型的变量的值的,但这有可能产生 InvallidCastException。C# 提供了使用模式匹配(pattern match)的格式转换(cast),仅当成功的时候会转换。C# 也提供了 isas 关键字来判断一个值是否为某个类型。

1.1 is 运算符

比如说以下代码:

1
2
3
4
5
6
7
8
9
10
C#复制代码static void FeedMammal(Animal a) {
if (a is Mammmal m)
{
m.Eat();
}
else
{
Console.WriteLine($"{a.GetType().Name} is not a Mammal");
}
}

重点在于:

  1. is 后面并不只是一个类型,而是声明了一个 Mammal 类型的变量。并不是说只能这么写。单单写 a is Mammal 也行,只是这种语法把类型判断和初始化写在一起,也是可行的一种语法。当判断成功的时候,a 的值会被赋予给了 m。
  2. m 的作用域仅仅在于 if 里,甚至连 else 里都无法访问。

1.2 as 运算符

请看以下代码:

1
2
3
4
5
6
7
8
9
10
11
C#复制代码static void TestForMammals(Object o) {
var m = o as Mammal;
if (m != null)
{
Console.WriteLine(m.ToString());
}
else
{
Console.WriteLine($"{o.GetType().Name} is not a Mammal");
}
}
  1. as 运算符执行一次转换。如果成功则转换成对应类型,不成功则返回 null。
  2. 顺便一提,上面的 m != null 也可以换成 m is not null

1.3 switch 做类型匹配

如下所示的语法也是可以的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
C#复制代码static void PatternMatchingSwitch(System.ValueType val)
{
switch (val)
{
case int number:
Console.WriteLine(number);
break;
case long number:
Console.WriteLine(number);
break;
case decimal number:
Console.WriteLine(number);
break;
case float number:
Console.WriteLine(number);
break;
case double number:
Console.WriteLine(number);
break;
case null:
Console.WriteLine("val is a nullable type with the null value");
break;
default:
Console.WriteLine("Could not convert " + val.ToString());
break;
}
}
  1. 模式匹配的场景

现代开发经常要用到来自各种不同地方的数据源,因此数据类型也都不一致。

于是文章采用了这么一个场景–在一个收费站收费。根据高峰期和车型收费。
难点在于,数据来源可能是多个不同的外部系统。那么首先假设有这么三个系统(3 个 namespace):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
C#复制代码namespace ConsumerVehicleRegistration
{
public class Car
{
public int Passengers { get; set; }
}
}

namespace CommercialRegistration
{
public class DeliveryTruck
{
public int GrossWeightClass { get; set; }
}
}

namespace LiveryRegistration
{
public class Taxi
{
public int Fares { get; set; }
}

public class Bus
{
public int Capacity { get; set; }
public int Riders { get; set; }
}
}

即,数据可能以不同的 class 形式存在。

2.1 最基础的收费

写一个最基础的收费类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
C#复制代码using System;
using CommercialRegistration;
using ConsumerVehicleRegistration;
using LiveryRegistration;

namespace toll_calculator
{
public class TollCalculator
{
public decimal CalculateToll(object vehicle) =>
vehicle switch
{
Car c => 2.00m,
Taxi t => 3.50m,
Bus b => 5.00m,
DeliveryTruck t => 10.00m,
{ } => throw new ArgumentException(message: "Not a known vehicle type", paramName: nameof(vehicle)),
null => throw new ArgumentNullException(nameof(vehicle))
};
}
}

这里使用了一个 switch expression 的语法(非 switch statement)。语法一看大概也知道是怎么回事。因为整个是一个 switch,因此 => 跟的就是 return 的值。

  1. { } 则是匹配所有的 非 null 的 object。必须写在后面,否则就被第一个返回了。
  2. null 则是匹配 null。

2.2 根据乘客收费

为了减少流量,让车辆载客数更高,因此希望乘客越少收费越高。

我们可以改写上面的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
C#复制代码public class TollCalculator
{
public decimal CalculateToll(object vehicle) =>
vehicle switch
{
Car {Passengers: 0} => 2.00m + 0.50m,
Car {Passengers: 1} => 2.0m,
Car {Passengers: 2} => 2.0m - 0.50m,
Car => 2.00m - 1.0m,

Taxi {Fares: 0} => 3.50m + 1.00m,
Taxi {Fares: 1} => 3.50m,
Taxi {Fares: 2} => 3.50m - 0.50m,
Taxi => 3.50m - 1.00m,

Bus b when ((double)b.Riders / (double)b.Capacity) < 0.50 => 5.00m + 2.00m,
Bus b when ((double)b.Riders / (double)b.Capacity) > 0.90 => 5.00m - 1.00m,
Bus => 5.00m,

DeliveryTruck t when (t.GrossWeightClass > 5000) => 10.00m + 5.00m,
DeliveryTruck t when (t.GrossWeightClass < 3000) => 10.00m - 2.00m,
DeliveryTruck => 10.00m,

{ } => throw new ArgumentException(message: "Not a known vehicle type", paramName: nameof(vehicle)),
null => throw new ArgumentNullException(nameof(vehicle))
};
}
  1. when 的用法也是简洁明了。当并等于某一个值,而是一个判断语句的时候用 when。
  2. 以上的代码有部分比较重复。比如对于 car 和 taxi,每个乘客数量都要写一整行代码。可以被简化为以下代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
C#复制代码public decimal CalculateToll(object vehicle) =>
vehicle switch
{
Car c => c.Passengers switch
{
0 => 2.00m + 0.5m,
1 => 2.0m,
2 => 2.0m - 0.5m,
_ => 2.00m - 1.0m
},

Taxi t => t.Fares switch
{
0 => 3.50m + 1.00m,
1 => 3.50m,
2 => 3.50m - 0.50m,
_ => 3.50m - 1.00m
},

Bus b when ((double)b.Riders / (double)b.Capacity) < 0.50 => 5.00m + 2.00m,
Bus b when ((double)b.Riders / (double)b.Capacity) > 0.90 => 5.00m - 1.00m,
Bus b => 5.00m,

DeliveryTruck t when (t.GrossWeightClass > 5000) => 10.00m + 5.00m,
DeliveryTruck t when (t.GrossWeightClass < 3000) => 10.00m - 2.00m,
DeliveryTruck t => 10.00m,

{ } => throw new ArgumentException(message: "Not a known vehicle type", paramName: nameof(vehicle)),
null => throw new ArgumentNullException(nameof(vehicle))
};
  1. 可以看到根本没有新的语法。而是再写一个 switch expression。
  2. _ 表示匹配其他所有情况。同理也不能写在前面,因为一定会被匹配上。

2.3 根据高峰时间收费

假设有这么一个需求。周末正常收费。工作日的话,早上的入流量和晚上的出流量双倍收费。其他时间 1.5 倍收费。凌晨则减少为 0.75。

如果写成 if 语句,写倒是可以写,但是效果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
C#复制代码public decimal PeakTimePremiumIfElse(DateTime timeOfToll, bool inbound)
{
if ((timeOfToll.DayOfWeek == DayOfWeek.Saturday) ||
(timeOfToll.DayOfWeek == DayOfWeek.Sunday))
{
return 1.0m;
}
else
{
int hour = timeOfToll.Hour;
if (hour < 6)
{
return 0.75m;
}
else if (hour < 10)
{
if (inbound)
{
return 2.0m;
}
else
{
return 1.0m;
}
}
else if (hour < 16)
{
return 1.5m;
}
else if (hour < 20)
{
if (inbound)
{
return 1.0m;
}
else
{
return 2.0m;
}
}
else // Overnight
{
return 0.75m;
}
}
}

可以用,但非常难读,也不好改。

2.3.1 使用模式匹配以及其他技巧来简化代码

仅仅使用模式匹配来匹配所有可能性也不好,依然复杂,因为我们有很多种组合情况。

2.3.1.1 周末还是工作日

第一个条件是是否为周末。那么专门为此写一个函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
C#复制代码// 注意 timeOfToll.DayOfWeek 和 DayOfWeek.Monday 中的 DayOfWeek 不是一个东西。
// 前者是 DateTime 类型的一个属性,后者是一个 enum 类型。
// 前者的值也为 DayOfWeek 类型
public static bool IsWeekday(DateTime timeOfToll) =>
timeOfToll.DayOfWeek switch {
DayOfWeek.Monday => true,
DayOfWeek.Tuesday => true,
DayOfWeek.Wednesday => true,
DayOfWeek.Thursday => true,
DayOfWeek.Friday => true,
DayOfWeek.Saturday => false,
DayOfWeek.Sunday => false
}

还可以再简化:

1
2
3
4
5
6
C#复制代码public static bool IsWeekday(DateTime timeOfToll) =>
timeOfToll.DayOfWeek switch {
DayOfWeek.Saturday => false,
DayOfWeek.Sunday => false,
_ => true
}

2.3.1.2 一天的时间段

先看代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
C#复制代码public enum TimeBand
{
MorningRush,
Daytime,
EvenignRush,
Overnight
}

public static TimeBand GetTimeBand(DateTime timeOfToll) =>
timeOfToll.Hour switch
{
> 19 or < 6 => TimeBand.Overnight,
< 10 => TimeBand.MorningRush,
> 16 => TimeBand.EvenignRush,
_ => TimeBand.Daytime
};
  1. 使用了 enum 来将一天的多个时间段分配值。
  2. 使用了 > 19 or < 6 这种语法,>< 以及 or 都是在 C# 9.0 后引入的。当然还有 >=<=andnot 这些语法。(什么你问为什么没有 = 的语法,因为不需要,直接写 6 就是 =6 了)

2.3.1.3 最终代码

有了以上两个函数后,代码就可以简化为这种 tuple pattern 形式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
C#复制代码public static decimal CalculateToll(DateTime timeOfToll, bool isInbound) =>
(IsWeekday(timeOfToll), GetTimeBand(timeOfToll), isInbound) switch
{
(true, TimeBand.MorningRush, true) => 2.00m,
(true, TimeBand.MorningRush, false) => 1.00m,
(true, TimeBand.Daytime, true) => 1.50m,
(true, TimeBand.Daytime, false) => 1.50m,
(true, TimeBand.EveningRush, true) => 1.00m,
(true, TimeBand.EveningRush, false) => 2.00m,
(true, TimeBand.Overnight, true) => 0.75m,
(true, TimeBand.Overnight, false) => 0.75m,
(false, TimeBand.MorningRush, true) => 1.00m,
(false, TimeBand.MorningRush, false) => 1.00m,
(false, TimeBand.Daytime, true) => 1.00m,
(false, TimeBand.Daytime, false) => 1.00m,
(false, TimeBand.EveningRush, true) => 1.00m,
(false, TimeBand.EveningRush, false) => 1.00m,
(false, TimeBand.Overnight, true) => 1.00m,
(false, TimeBand.Overnight, false) => 1.00m,
};

当然,很多条件可以简化:

1
2
3
4
5
6
7
8
9
10
11
C#复制代码public static decimal CalculateToll(DateTime timeOfToll, bool isInbound) =>
(IsWeekday(timeOfToll), GetTimeBand(timeOfToll), isInbound) switch
{
(true, TimeBand.MorningRush, true) => 2.00m,
(true, TimeBand.MorningRush, false) => 1.00m,
(true, TimeBand.Daytime, _) => 1.50m,
(true, TimeBand.EveningRush, true) => 1.00m,
(true, TimeBand.EveningRush, false) => 2.00m,
(true, TimeBand.Overnight, _) => 0.75m,
(false, _, _) => 1.00m,
};

然后可以把 3 个返回 1.00m 的用 _ 代替:

1
2
3
4
5
6
7
8
9
C#复制代码public static decimal CalculateToll(DateTime timeOfToll, bool isInbound) =>
(IsWeekday(timeOfToll), GetTimeBand(timeOfToll), isInbound) switch
{
(true, TimeBand.MorningRush, true) => 2.00m,
(true, TimeBand.Daytime, _) => 1.50m,
(true, TimeBand.EveningRush, false) => 2.00m,
(true, TimeBand.Overnight, _) => 0.75m,
_ => 1.00m,
};

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

0%