在下面的例子中,我们将比较F#和C#用代码来实现网页的下载,其利用回调函数来处理文本流。
让我们直接看F#的实现
// "open" 声明.net的命名空间,使命名空间在例子可见 open System.Netopen Systemopen System.IO
// 获取网页的内容 let fetchUrl callback url =
let req = WebRequest.Create(Uri(url))
use resp = req.GetResponse()
use stream = resp.GetResponseStream()
use reader = new IO.StreamReader(stream)
callback reader url
让我们一起来走读下代码:
- 在顶部使用"open"让我们可以使用"WebRequest"而不是"System.Net.WebRequest",他就像使用 "
using System.Net
" 在C#顶部。 - 接下来,我们定义了函数
fetchUrl
,它接受两个参数,一个实处理文本流的毁掉函数,一个是获取网页的url地址. - 接下来我们封装了url字符到一个Uri对象中. F# 拥有严格的类型检查, 如果我们用下面这几替代之前写的(
let req = WebRequest.Create(url)
)编译器将会提示,他不知道应该使用哪个版本的WebRequest.Create
. - 在定义
response
,stream
和reader
值时, 我们使用了哦 "use
"关键字代替"let
".他只被用在链接实现了IDisposable接口的类
.它告诉编译器会自动处理资源当它出了作用域后。这就像C#中使用C# "using
" 关键字. - 最后一行掉用了代StreamReader和url参数的回调函数。注意这个回调类型并没有制定类型现在让我们看看相对应的C#实现.
class WebPageDownloader {
public TResult FetchUrl<TResult>(
string url,
Func<string, StreamReader, TResult> callback)
{
var req = WebRequest.Create(url);
using (var resp = req.GetResponse())
{
using (var stream = resp.GetResponseStream())
{
using (var reader = new StreamReader(stream))
{
return callback(url, reader);
}
}
}
} }
就像之前一样,C#的代码有很多“噪声”。
- 其中有10函只是纯粹的花括号。
- 有5个层级的缩进增加了视觉的复制性。
- 所有的参数类型都必须显示的定义,即使是泛型的
TResult
类型也被重复了三次。
*是的,在一些特殊的例子中,有些括号和缩进可以被删除,但是大部分的情况下他们还是必须的
测试代码
回到F#我们可以直接在交互式窗口进行测试
let myCallback (reader:IO.StreamReader) url =
let html = reader.ReadToEnd()
let html1000 = html.Substring(0,1000)
printfn "Downloaded %s. First 1000 is %s" url html1000
html // 返回所有html
//测试
let google = fetchUrl myCallback "http://google.com"
最后我们必须对reader参数进行类型定义
(
reader:IO.StreamReader
). 这是必须的因为F#编译器不能自动的判定“reader”的类型。
在F#中有一个很有用的特性, 你可以“bake in”(个人觉得有点像C#的缺省默认参数,但是又有不同)参数到一个函数,那样他就不用每次都传递参数。这就是为什么
url
参数放在最后一个而不是第一个
, 在C#代码中,一旦回调函数设置了,url变量就从调用者传递到被调用者.
// 构建一个函数将回调函数“baked in" let fetchUrl2 = fetchUrl myCallback
// 测试 let google = fetchUrl2 "http://www.google.com" let bbc = fetchUrl2 "http://news.bbc.co.uk"
// 测试的网站序列 let sites = ["http://www.bing.com";
"http://www.google.com";
"http://www.yahoo.com"]
// 处理序列中的每一网站 sites |> List.map fetchUrl2
最后一行 (使用 List.map
)他让我们可以利用一个新的函数很容易结合list处理函数,一次性的下载整个list的网页
下面是相对应的C#测试代码:
[Test]public void TestFetchUrlWithCallback(){ Func<string, StreamReader, string> myCallback = (url, reader) => { var html = reader.ReadToEnd(); var html1000 = html.Substring(0, 1000); Console.WriteLine( "Downloaded {0}. First 1000 is {1}", url, html1000); return html; }; var downloader = new WebPageDownloader(); var google = downloader.FetchUrl("http://www.google.com", myCallback); // 测试的网站序列 var sites = new List<string> { "http://www.bing.com", "http://www.google.com", "http://www.yahoo.com"}; // 处理序列中的每个网站 sites.ForEach(site => downloader.FetchUrl(site, myCallback));}
在此会发现,C#代码相比F#有更多的“噪声”,许多现实的类型引用,更重要的是C#代码不能很容易的让你bake in参数到函数中。因此回调函数必须每次都显示的引用。