函数编程
本章主要讲解 C#7的新特性,比如本地函数、元组和模式匹配。
本地函数
本地函数在方法的作用域、属性访问器、构造函 数或者lambda表达式内声明。本地函数只能在包含成员的作用域内调用。可以使用本地函数,而不是使用仅一 个地方需要的私有方法。
private static void IntroWithLambdaExpression()
{
Func<int, int, int> add = (x, y) =>
{
return x + y;
};
int result = add(37, 5);
Console.WriteLine(result);
}
static void IntroWithLocalFunctions()
{
int add(int x, int y)
{
return x + y;
}
int result = add(37, 5);
Console.WriteLine(result);
}
private static void IntroWithLocalFunctionsWithExpressionBodies()
{
int add(int x, int y) => x + y;
int result = add(37, 5);
Console.WriteLine(result);
}
private static void IntroWithLocalFunctionsWithClosures()
{
int z = 3;
int result = add(37, 5);
Console.WriteLine(result);
int add(int x, int y) => x + y + z;
}
递归本地函数
另一个使用本地函数的场景是递归调用,如下面使用Quicksort方法的示例所示。这里,本地函数Sort是
递归调用的,直到集合排好序为止
public static void QuickSort<T>(T[] elements) where T : IComparable<T>
{
void Sort(int start, int end)
{
int i = start, j = end;
var pivot = elements[(start + end) / 2];
while (i <= j)
{
while (elements[i].CompareTo(pivot) < 0) i++;
while (elements[j].CompareTo(pivot) > 0) j--;
if (i <= j)
{
T tmp = elements[i];
elements[i] = elements[j];
elements[j] = tmp;
i++;
j--;
}
}
if (start < j) Sort(start, j);
if (i < end) Sort(i, end);
}
Sort(0, elements.Length - 1);
}
元组
元组能够组合不同类型的对象。使用数组可以组合相同类型的对象,而元组允许使用类型的不同组合。元 组有助于减少以下两个需求:
- 定义自定义类或结构,以返回多个值
- 定义参数,从方法中返回多个值
元组的声明和初始化
可以使用圆括号声明一个元组,并使用通过括号创建的元组字面量来初始化。
private static void IntroTuples()
{
(string s, int i, Person p) t = ("magic", 42, new Person("Matthias", "Nagel"));
Console.WriteLine($"s: {t.s}, i: {t.i}, p: {t.p}");
var t2 = ("magic", 42, new Person("Matthias", "Nagel"));
Console.WriteLine($"string: {t2.Item1}, int: {t2.Item2}, person: {t2.Item3}");
var t3 = (s: "magic", i: 42, p: new Person("Matthias", "Nagel"));
Console.WriteLine($"s: {t3.s}, i: {t3.i}, p: {t3.p}");
(string astring, int anumber, Person aperson) t4 = t3;
Console.WriteLine($"s: {t4.astring}, i: {t4.anumber}, p: {t4.aperson}");
}
元组解构
还可以将元组分解为变量。为此,只需要从前面的代码示例中删除元组变量,并在括号中定义变量名。然 后可以直接访问变量,其中包含元组部分的值
private static void TupleDeconstruction()
{
(string s, int i, Person p) = ("magic", 42, new Person("Stephanie", "Nagel"));
Console.WriteLine($"s: {s}, i: {i}, p: {p}");
(var s1, var i1, var p1) = ("magic", 42, new Person("Stephanie", "Nagel"));
Console.WriteLine($"s: {s1}, i: {i1}, p: {p1}");
string s2;
int i2;
Person p2;
(s2, i2, p2) = ("magic", 42, new Person("Katharina", "Nagel"));
Console.WriteLine($"s: {s2}, i: {i2}, p: {p2}");
(string s3, _, _) = ("magic", 42, new Person("Katharina", "Nagel"));
Console.WriteLine(s3);
}
元组的返回
static (int result, int remainder) Divide(int dividend, int divisor)
{
int result = dividend / divisor;
int remainder = dividend % divisor;
return (result, remainder);
}
模式匹配
从面向对象的观点来看,最好总是使用具体的类型和接口来解决问题。然而,通常这并不容易做到。在数 据库中,查询可能会给出与任何层次结构都无关的不同对象类型。访问API服务时,可以返回一个列表或对象, 或者可能什么也不返回。因此,方法通常应该与不同的类型一起工作。这就是模式匹配可以提供帮助的地方。
模式匹配与is运算符
与is运算符的简单匹配是const模式。在这个模式中,可以将对象与常量值进行比较
static void IsOperator(object item)
{
// const pattern
if (item is null)
{
Console.WriteLine("item is null");
}
if (item is 42)
{
Console.WriteLine("item is 42");
}
// type pattern
if (item is int)
{
Console.WriteLine($"Item is of type int");
}
if (item is int i)
{
Console.WriteLine($"Item is of type int with a value {i}");
}
if (item is string s)
{
Console.WriteLine($"Item is a string: {s}");
}
if (item is Person p && p.FirstName.StartsWith("Ka"))
{
Console.WriteLine($"Item is a person: {p.FirstName} {p.LastName}");
}
if (item is IEnumerable<Person> people)
{
string names = string.Join(", ", people.Select(p1 => p1.FirstName).ToArray());
Console.WriteLine($"it's a Person collection containing {names}");
}
// var pattern
if (item is var every)
{
Console.WriteLine($"it's var of type {every?.GetType().Name ?? "null"} with the value {every ?? "nothing"}");
}
}
模式匹配与switch语句
对于switch语句,也可以使用三种模式类型。
static void SwitchStatement(object item)
{
switch (item)
{
case null:
case 42:
Console.WriteLine("it's a const pattern");
break;
case int i:
Console.WriteLine($"it's a type pattern with int: {i}");
break;
case string s:
Console.WriteLine($"it's a type pattern with string: {s}");
break;
case Person p when p.FirstName == "Katharina":
Console.WriteLine($"type pattern match with Person and when clause: {p}");
break;
case Person p:
Console.WriteLine($"type pattern match with Person: {p}");
break;
case var every:
Console.WriteLine($"var pattern match: {every?.GetType().Name}");
break;
default:
}
}
模式匹配与泛型
如果需要与泛型相匹配的模式,则需要将编译器配置为至少C# 7.1 C# 7.1为泛型添加了模式匹配。
public class HealthPackage
{
public void CheckHealth()
{
}
}
public class HttpManager
{
public void Send<T>(T package)
{
if (package is HealthPackage hp)
{
hp.CheckHealth();
}
//...
}
}