5.2.1 F# 和 C# 中的多值

时间:2022-09-02 00:26:51

5.2.1 F# 和 C# 中的多值

 

    我们在第三章讨论元组时,用 C# 实现了一个 Tuple 类,与 F# 中的元组有相同的行为。使用元组不是从 C# 中的方法中返回多值的通常做法,你仍会发现,以函数方式写代码还是有用的。如果想在 C# 中写这个,而不使用元组,或者为每一个返回多值的方法声明一个新的类,你可能使用 out 参数。可以在清单 5.1 中看到这两种方法,这里,我们实现一个简单的函数程序,有余数的除法。

 

Listing 5.1 Division with a remainder (F# and C#)

 

// F# version using tuples
> let divRem(a, b) = (a / b, a % b);;
val divRem : int * int -> int * int

> let (res, rem) = divRem(10, 3);;
val res : int = 3
val rem : int = 1

// C# version using out parameter
int DivRem(int a, int b, out int rem) {
rem = a % b;
return a / b;
}
int rem;
int res = DivRem(10, 3, out rem);

 

    F# 版本的代码展示了 F# Interactive 的输出,但是,如你所见,代码更短。这是因为,从一函数返回多值,在 F# 中比 C# 更加重要。C# 3.0 有另外一种方法表示多值,叫匿名类型(anonymous types)。这有些限制,因为,它只在一个方法内容易使用,但仍很有趣。

 

C# 3.0 中的匿名类型

 

    由 Language Integrated Query (LINQ) 增加的关键功能是写查询的能力(我们将在第 11 章深度讨论有关查询的问题)。查询使用集合,例如,我们可能筛选产品的集合,只选择一个特定种类的产品,然后,返回每种产品的名字和价格。这就需要匿名类型,因为,在返回名字和价格时,我们必须有效地返回多值:

var query = from p in data.Products
where p.CategoryID == 1
select new { Name = p.ProductName, Price = p.UnitPrice };
foreach(var result in query)
Console.WriteLine(result.Name);

    匿名类型与元组之间的不同在于,匿名类型的元素是命名的。名字由查询中创建类型的代码指定,能够在以后用于读取元素的值。我们可以重写前面的示例,使用匿名类型:

int a = 10, b = 3;
var r = new { Remainder = a % b, Result = a / b };
Console.WriteLine("result={0}, remainder={1}", r.Result, r.Remainder);

    这个不是特别有用,因为,匿名类型只能用于本地。当我们从这个方法中返回匿名类型时,丢失了编译时的类型信息,再也不可能很容易存取属性了。

 

    我们已经看到了,在 C# 中,out 参数与 F# 中元组的使用目的相同,你可能想知道如何从 F# 中,使用现成的、有 out 参数的 .NET 方法,很幸运,语言有一个极好的功能,就专此目的而设。

 

使用元组代替 out 参数

 

    如果确实想,那么,从 F# 中也可能使用 out 参数,但是,元组通常是首选,因为,F# 自动将有 out 参数的 .NET 方法暴露成返回元组的方法,因此,你不需要做任何事情,它是透明的。这就是说,你的 F# 代码看起来仍然像惯用的函数代码,即使它调用的代码没有元组的概念。在 .NET 中,最广泛使用的有 out 参数的方法可能是 TryParse,它可用于所有的数值类型,比如,Int32。要在 F# Interactive 中使用这个方法,将首先需要打开 System 命名空间。我们来看一个在 C# 中使用的示例(使用out 参数):

int parsed;
bool success = Int32.TryParse("41", out parsed);

and F# (using tuples)
let (success, parsed) = Int32.TryParse("41");;

    F# 版本写起来相当容易,看起来更像“函数”,因为,它避免了把参数值作为引用传递给可变的变量。在这个示例中,我们使用模式匹配来分解返回的元组,但是,如果你想完全忽略成功的标记,也可以使用 snd 函数,从返回的元组中只挑选了数值。当解析失败时,返回的将是这个类型的默认值,即 0。另外,我们也可以写一个工具函数,让我们指定其默认值。我们将在下一章学习写这样的函数。

    现在,在看 F# 中使用元组的最好实践之前,暂时回到有关值与类型的讨论,重温元组类型,以及这些类型的值如何构造的。