Phi*_* P. 8 c# f# functional-programming idisposable sequences
这是一个假设的场景.
我有非常多的用户名(比如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
Run Code Online (Sandbox Code Playgroud)
美丽?优雅?绝对.
但! 谁和什么时候处置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
}
Run Code Online (Sandbox Code Playgroud)
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;
}
}
Run Code Online (Sandbox Code Playgroud)
我执行的测试表明,即使并行执行内容,对象也会在正确的点上正确处理:一次在共享连接的整个迭代结束时; 并且在非共享连接的每个迭代周期之后.
所以,问题:我做对了吗?
编辑:
有些答案在代码中指出了一些错误,我做了一些修改.编译的完整工作示例如下.
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
Run Code Online (Sandbox Code Playgroud)
结果是:
共享连接:"你好""你好"......"处置"
个人关系:"你好""处置""你好""处置"
我会避免这种方法,因为如果你的库的不知情的用户做了类似的事情,那么结构就会失败
users()
|> Seq.map userToCxn
|> Seq.toList() //oops disposes connections
|> List.map .... // uses disposed cxns
. . ..
Run Code Online (Sandbox Code Playgroud)
我不是这个问题的专家,但我认为这是一个最好的做法,不要让序列/ IEnumerables在它们产生它们之后弄乱它们,因为中间的ToList()调用会产生不同于结果的结果直接在序列上 - DoSomething(GetMyStuff())将与DoSomething(GetMyStuff().ToList())不同.
实际上,为什么不只是使用序列表达式来完成整个事情,因为这样可以完全解决这个问题:
seq{ for user in users do
use cxn = userToCxn user
yield cxnToResult cxn }
Run Code Online (Sandbox Code Playgroud)
(其中userToCxn和cxnToResult都是简单的一对一非处置函数).这似乎比任何东西都更具可读性,并且应该产生所需的结果,可以并行化,并适用于任何一次性用品.可以使用以下技术将其转换为C#LINQ:http://solutionizing.net/2009/07/23/using-idisposables-with-linq/
from user in users
from cxn in UserToCxn(user).Use()
select CxnToResult(cxn)
Run Code Online (Sandbox Code Playgroud)
另一个需要考虑的是首先定义你的"getSomethingForAUserAndDisposeTheResource"函数,然后将它作为你的基本构建块:
let getUserResult selector user =
use cxn = userToCxn user
selector cxn
Run Code Online (Sandbox Code Playgroud)
一旦你有了这个,那么你可以从那里轻松地建立起来:
//first create a selector
let addrSelector cxn = cxn.Address()
//then use it like this:
let user1Address1 = getUserResult addrSelector user1
//or more idiomatically:
let user1Address2 = user1 |> getUserResult addrSelector
//or just query dynamically!
let user1Address3 = user1 |> getUserResult (fun cxn -> cxn.Address())
//it can be used with Seq.map easily too.
let addresses1 = users |> Seq.map (getUserResult (fun cxn -> cxn.Address()))
let addresses2 = users |> Seq.map (getUserResult addrSelector)
//if you are tired of Seq.map everywhere, it's easy to create your own map function
let userCxnMap selector = Seq.map <| getUserResult selector
//use it like this:
let addresses3 = users |> userCxnMap (fun cxn -> cxn.Address())
let addresses4 = users |> userCxnMap addrSelector
Run Code Online (Sandbox Code Playgroud)
这样,如果您想要的只是一个用户,则不会提交检索整个序列.我想这里学到的经验是让你的核心功能变得简单,并且可以更容易地在它上面构建抽象.请注意,如果您在中间某处执行ToList,则这些选项都不会失败.