Here is a hypotetical scenario.
I have very large number of user names (say 10,000,000,000,000,000,000,000. Yes, we are in the intergalactic age :)). Each user has its own database. I need to iterate through the list of users and execute some SQL against each of the databases and print the results.
Because I learned the goodness of functional programming and because I deal with such a vast number of users, I decide to implement this using F# and pure sequences, (aka IEnumerable). And here I go.
// 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
Beautiful? Elegant? Absolutely.
But! Who and at what point disposes of the SqlConnections?
And I don't thing the answer mapConnectionToResult
should do it is right, because it knows nothing about the lifetime of the connection it is given. And things may work or not work depending on how mapUsersToConnections
is implemented and various other factors.
As mapUsersToConnections
is the only other place which has access to connection it must be its responsibility to dispose of SQL connection.
In F#, this can be done like this:
// 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
}
The C# equivalient would be:
// 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;
}
}
The tests I performed suggest that the objects do get disposed correctly at correct points, even if executing stuff in parallel: once at the end of the whole iteration for the shared connection; and after each iteration cycle for the non-shared connection.
So, THE QUESTION: Did I get this right?
EDIT:
Some answers kindly pointed out some errors in the code, and I made some corrections. The complete working example which compiles is below.
The use of SqlConnection is for example purposes only, it's any IDisposable really.
Example which compiles
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
The results are:
Shared connection: "hello" "hello" ... "Disposing"
Individual connections: "hello" "Disposing" "hello" "Disposing"