【发布时间】:2011-10-10 08:58:14
【问题描述】:
这是一个假设的场景。
我有大量的用户名(比如 10,000,000,000,000,000,000,000。是的,我们处于星际时代 :))。每个用户都有自己的数据库。我需要遍历用户列表并对每个数据库执行一些 SQL 并打印结果。
因为我学到了函数式编程的优点,并且因为我要与如此大量的用户打交道,所以我决定使用 F# 和纯序列(又名 IEnumerable)来实现它。我来了。
// gets the list of user names
let users() : seq<string> = ...
// maps user name to the SqlConnection
let mapUsersToConnections (users: seq<string>) : seq<SqlConnection> = ...
// executes some sql against the given connection and returns some result
let mapConnectionToResult (conn) : seq<string> = ...
// print the result
let print (result) : unit = ...
// and here is the main program
users()
|> mapUsersToConnections
|> Seq.map mapConnectionToResult
|> Seq.iter print
漂亮吗?优雅的?绝对的。
但是! 谁以及在什么时候处理 SqlConnections?
而且我不认为mapConnectionToResult 应该这样做 的答案是正确的,因为它对所提供的连接的生命周期一无所知。根据mapUsersToConnections 的实施方式和其他各种因素,事情可能会奏效或无法奏效。
由于mapUsersToConnections 是唯一可以访问连接的其他地方,因此它必须负责处理 SQL 连接。
在 F# 中,可以这样完成:
// implementation where we return the same connection for each user
let mapUsersToConnections (users) : seq<SqlConnection> = seq {
use conn = new SqlConnection()
for u in users do
yield conn
}
// implementation where we return new connection for each user
let mapUsersToConnections (users) : seq<SqlConnection> = seq {
for u in users do
use conn = new SqlConnection()
yield conn
}
C# 等效项是:
// C# -- same connection for all users
IEnumerable<SqlConnection> mapUsersToConnections(IEnumerable<string> users)
{
using (var conn = new SqlConnection())
foreach (var u in users)
{
yield return conn;
}
}
// C# -- new connection for each users
IEnumerable<SqlConnection> mapUsersToConnections(IEnumerable<string> user)
{
foreach (var u in users)
using (var conn = new SqlConnection())
{
yield return conn;
}
}
我执行的测试表明,即使并行执行某些内容,对象也确实可以在正确的位置正确处理:在共享连接的整个迭代结束时一次;并在非共享连接的每个迭代周期之后。
那么,问题:我做对了吗?
编辑:
一些答案好心指出了代码中的一些错误,我做了一些更正。编译的完整工作示例如下。
SqlConnection 的使用仅用于示例目的,它实际上是任何 IDisposable。
编译示例
open System
// Stand-in for SqlConnection
type SimpeDisposable() =
member this.getResults() = "Hello"
interface IDisposable with
member this.Dispose() = printfn "Disposing"
// Alias SqlConnection to our dummy
type SqlConnection = SimpeDisposable
// gets the list of user names
let users() : seq<string> = seq {
for i = 0 to 100 do yield i.ToString()
}
// maps user names to the SqlConnections
// this one uses one shared connection for each user
let mapUsersToConnections (users: seq<string>) : seq<SqlConnection> = seq {
use c = new SimpeDisposable()
for u in users do
yield c
}
// maps user names to the SqlConnections
// this one uses new connection per each user
let mapUsersToConnections2 (users: seq<string>) : seq<SqlConnection> = seq {
for u in users do
use c = new SimpeDisposable()
yield c
}
// executes some "sql" against the given connection and returns some result
let mapConnectionToResult (conn:SqlConnection) : string = conn.getResults()
// print the result
let print (result) : unit = printfn "%A" result
// and here is the main program - using shared connection
printfn "Using shared connection"
users()
|> mapUsersToConnections
|> Seq.map mapConnectionToResult
|> Seq.iter print
// and here is the main program - using individual connections
printfn "Using individual connection"
users()
|> mapUsersToConnections2
|> Seq.map mapConnectionToResult
|> Seq.iter print
结果是:
共享连接: “你好” “你好” ... “处置”
个人连接: “你好” “处置” “你好” “处置”
【问题讨论】:
-
10 亿用户。我认为您的系统中有一些多帐户。
-
请注意:不要忘记在这种情况下对函数式编程的热爱:纯粹的函数式编程定义为没有副作用的编码。 IDisposable.Dispose()(以及几乎任何返回 void 的 任何东西)仅针对其副作用执行,因此根据定义,它是纯函数式编码的相反。
标签: c# f# functional-programming idisposable sequences