c# - IEnumerable<IDisposable>: who disposes of what and when -- Did I get it right? -
here hypotetical scenario.
i have large number of user names (say 10,000,000,000,000,000,000,000. yes, in intergalactic age :)). each user has own database. need iterate through list of users , execute sql against each of databases , print results.
because learned goodness of functional programming , because deal such vast number of users, decide implement using f# , pure sequences, (aka ienumerable). , here go.
// gets list of user names let users() : seq<string> = ... // maps user name sqlconnection let mapuserstoconnections (users: seq<string>) : seq<sqlconnection> = ... // executes sql against given connection , returns result let mapconnectiontoresult (conn) : seq<string> = ... // print result let print (result) : unit = ... // , here main program users() |> mapuserstoconnections |> seq.map mapconnectiontoresult |> seq.iter print
beautiful? elegant? absolutely.
but! who , @ point disposes of sqlconnections?
and don't thing answer mapconnectiontoresult
should it right, because knows nothing lifetime of connection given. , things may work or not work depending on how mapuserstoconnections
implemented , various other factors.
as mapuserstoconnections
other place has access connection must responsibility dispose of sql connection.
in f#, can done this:
// implementation return same connection each user let mapuserstoconnections (users) : seq<sqlconnection> = seq { use conn = new sqlconnection() u in users yield conn } // implementation return new connection each user let mapuserstoconnections (users) : seq<sqlconnection> = seq { u in users use conn = new sqlconnection() yield conn }
the c# equivalient be:
// c# -- same connection users ienumerable<sqlconnection> mapuserstoconnections(ienumerable<string> users) { using (var conn = new sqlconnection()) foreach (var u in users) { yield return conn; } } // c# -- new connection each users ienumerable<sqlconnection> mapuserstoconnections(ienumerable<string> user) { foreach (var u in users) using (var conn = new sqlconnection()) { yield return conn; } }
the tests performed suggest objects disposed correctly @ correct points, if executing stuff in parallel: once @ end of whole iteration shared connection; , after each iteration cycle non-shared connection.
so, the question: did right?
edit:
some answers kindly pointed out errors in code, , made corrections. complete working example compiles below.
the use of sqlconnection example purposes only, it's idisposable really.
example compiles
open system // stand-in sqlconnection type simpedisposable() = member this.getresults() = "hello" interface idisposable member this.dispose() = printfn "disposing" // alias sqlconnection our dummy type sqlconnection = simpedisposable // gets list of user names let users() : seq<string> = seq { = 0 100 yield i.tostring() } // maps user names sqlconnections // 1 uses 1 shared connection each user let mapuserstoconnections (users: seq<string>) : seq<sqlconnection> = seq { use c = new simpedisposable() u in users yield c } // maps user names sqlconnections // 1 uses new connection per each user let mapuserstoconnections2 (users: seq<string>) : seq<sqlconnection> = seq { u in users use c = new simpedisposable() yield c } // executes "sql" against given connection , returns result let mapconnectiontoresult (conn:sqlconnection) : string = conn.getresults() // print result let print (result) : unit = printfn "%a" result // , here main program - using shared connection printfn "using shared connection" users() |> mapuserstoconnections |> seq.map mapconnectiontoresult |> seq.iter print // , here main program - using individual connections printfn "using individual connection" users() |> mapuserstoconnections2 |> seq.map mapconnectiontoresult |> seq.iter print
the results are:
shared connection: "hello" "hello" ... "disposing"
individual connections: "hello" "disposing" "hello" "disposing"
i'd avoid approach since structure fail if unwitting user of library did like
users() |> seq.map usertocxn |> seq.tolist() //oops disposes connections |> list.map .... // uses disposed cxns . . ..
i'm not expert on issue, i'd presume it's best practice not have sequences / ienumerables muck things after yield them, reason intermediary tolist() call produce different results acting on sequence directly -- dosomething(getmystuff()) different dosomething(getmystuff().tolist()).
actually, why not use sequence expressions whole thing, around issue entirely:
seq{ user in users use cxn = usertocxn user yield cxntoresult cxn }
(where usertocxn , cxntoresult both simple one-to-one non-disposing functions). seems more readable , should yield desired results, parallelizable, , works disposable. can translated c# linq using following technique: http://solutionizing.net/2009/07/23/using-idisposables-with-linq/
from user in users cxn in usertocxn(user).use() select cxntoresult(cxn)
another take on though define "getsomethingforauseranddisposetheresource" function first, , use fundamental building block:
let getuserresult selector user = use cxn = usertocxn user selector cxn
once have this, can build there:
//first create selector let addrselector cxn = cxn.address() //then use this: let user1address1 = getuserresult addrselector user1 //or more idiomatically: let user1address2 = user1 |> getuserresult addrselector //or query dynamically! let user1address3 = user1 |> getuserresult (fun cxn -> cxn.address()) //it can used seq.map too. let addresses1 = users |> seq.map (getuserresult (fun cxn -> cxn.address())) let addresses2 = users |> seq.map (getuserresult addrselector) //if tired of seq.map everywhere, it's easy create own map function let usercxnmap selector = seq.map <| getuserresult selector //use this: let addresses3 = users |> usercxnmap (fun cxn -> cxn.address()) let addresses4 = users |> usercxnmap addrselector
that way aren't committed retrieving entire sequence if want 1 user. guess lesson learned here make core functions simple , makes easier build abstractions on top of it. , note none of these options fail if tolist somewhere in middle.
Comments
Post a Comment