Вот гипотетический сценарий.
У меня очень большое количество имен пользователей (скажем, 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;
}
}
Проведенные мною тесты показывают, что объекты правильно располагаются в правильных точках, даже при параллельном выполнении объектов: один раз в конце всей итерации для общего подключения; и после каждого цикла итерации для не общего соединения.
Итак, ВОПРОС: Я правильно понял?
РЕДАКТИРОВАТЬ :
Некоторые ответы любезно указывают на некоторые ошибки в коде, и я внес некоторые исправления. Полный рабочий пример, который компилируется ниже.
Использование 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
Результаты:
Совместное подключение:
"Привет"
"Привет"
...
"Располагая"
Индивидуальные подключения:
"Привет"
«Располагая»
"Привет"
"Располагая"