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:

  1. some answers kindly pointed out errors in code, , made corrections. complete working example compiles below.

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

Popular posts from this blog

linux - Using a Cron Job to check if my mod_wsgi / apache server is running and restart -

actionscript 3 - TweenLite does not work with object -

jQuery Ajax Render Fragments OR Whole Page -