gpt4 book ai didi

c# - IEnumerable : who disposes of what and when -- Did I get it right?

转载 作者:太空狗 更新时间:2023-10-29 18:08:45 24 4
gpt4 key购买 nike

这是一个假设场景。

我有很多用户名(比如 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;
}
}

我执行的测试表明对象确实在正确的点被正确处理,即使并行执行的东西也是如此:一次在共享连接的整个迭代结束时;在非共享连接的每个迭代周期之后。

那么,问题:我做对了吗?

编辑:

  1. 有些回答友善地指出了代码中的一些错误,我也做了一些修正。编译的完整工作示例如下。

  2. 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

结果是:

共享连接:“你好”“你好”...“处置”

个别连接:“你好”“处置”“你好”“处置”

最佳答案

我会避免这种方法,因为如果你的库的不知情用户做了类似的事情,结构就会失败

users()
|> Seq.map userToCxn
|> Seq.toList() //oops disposes connections
|> List.map .... // uses disposed cxns
. . ..

我不是这个问题的专家,但我认为最好的做法是不要让序列/IEnumerables 在它们产生它们之后弄乱它们,因为中间的 ToList() 调用会产生不同的结果而不是直接作用于序列——DoSomething(GetMyStuff()) 将不同于 DoSomething(GetMyStuff().ToList())。

实际上,为什么不对整个事情使用序列表达式,因为这样可以完全解决这个问题:

seq{ for user in users do
use cxn = userToCxn user
yield cxnToResult cxn }

(其中 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)

不过,另一种做法是先定义“getSomethingForAUserAndDisposeTheResource”函数,然后将其用作基本构建 block :

let getUserResult selector user = 
use cxn = userToCxn user
selector cxn

一旦有了这个,就可以轻松地从那里开始构建:

 //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

如果您只需要一个用户,那么您就不必致力于检索整个序列。我想这里吸取的教训是让你的核心功能变得简单,这样可以更容易地在它之上构建抽象。请注意,如果您在中间某处执行 ToList,这些选项都不会失败。

关于c# - IEnumerable<IDisposable> : who disposes of what and when -- Did I get it right?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/6778958/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com