Я не верю, что когда вы используете BeginInvoke()
и EndInvoke()
с ref
или out
аргументами, вы действительно передаёте переменные по ссылке. факт, что мы должны вызывать EndInvoke()
с параметром ref
, должен быть ключом к этому.
Давайте изменим ваш пример, чтобы продемонстрировать поведение, которое я описываю:
void A()
{
int x = 100;
int z = 400;
RefAction<int> b = B;
//b.BeginInvoke(ref x, C, null);
var ar = b.BeginInvoke(ref x, null, null);
b.EndInvoke(ref z, ar);
Console.WriteLine(x); // outputs '100'
Console.WriteLine(z); // outputs '101'
}
Если вы посмотрите на вывод сейчас, вы увидите, что значение x
фактически не изменилось. Но z
теперь содержит значение обновления.
Я подозреваю, что компилятор изменяет семантику передачи переменных на ref
, когда вы используете асинхронные методы Begin / EndInvoke.
После просмотра IL, созданного этим кодом, выясняется, что ref
аргументы BeginInvoke()
все еще передаются by ref
. Хотя Reflector не показывает IL для этого метода, я подозреваю, что он просто не передает параметр в качестве аргумента ref
, а вместо этого создает отдельную переменную за сценой для передачи в B()
. Когда вы затем вызываете EndInvoke()
, вы должны снова ввести аргумент ref
, чтобы получить значение из асинхронного состояния. Вполне вероятно, что такие аргументы на самом деле хранятся как часть (или в сочетании с) объекта IAsyncResult
, который необходим для окончательного получения их значений.
Давайте подумаем, почему поведение, вероятно, работает таким образом. Когда вы делаете асинхронный вызов метода, вы делаете это в отдельном потоке. Этот поток имеет свой собственный стек и поэтому не может использовать типичный механизм псевдонимов переменных ref/out
. Однако, чтобы получить какие-либо возвращаемые значения из асинхронного метода, вам необходимо в конечном итоге вызвать EndInvoke()
, чтобы завершить операцию и получить эти значения. Однако вызов EndInvoke()
может происходить так же легко в совершенно другом потоке, что и исходный вызов BeginInvoke()
или фактическое тело метода. Ясно, что стек вызовов не является подходящим местом для хранения таких данных - тем более что поток, используемый для асинхронного вызова, может быть переопределен для другого метода после завершения асинхронной операции. В результате для «маршалирования» возвращаемого значения и аргументов out / ref из метода, вызываемого обратно на сайт, где они в конечном итоге будут использоваться, необходим какой-то другой механизм, кроме стека.
Я считаю, что этот механизм (в реализации Microsoft .NET) является объектом IAsyncResult
. Фактически, если вы изучите объект IAsyncResult
в отладчике, вы заметите, что в непубличных членах существует _replyMsg
, который содержит коллекцию Properties
. Эта коллекция содержит такие элементы, как __OutArgs
и __Return
, данные которых отражают их тезки.
РЕДАКТИРОВАТЬ: Вот теория о дизайне асинхронного делегата, который мне приходит в голову. Кажется вероятным, что подписи BeginInvoke()
и EndInvoke()
были выбраны, чтобы быть как можно ближе друг к другу, чтобы избежать путаницы и улучшить ясность. Метод BeginInvoke()
на самом деле не требует для принятия ref/out
аргументов - поскольку ему нужно только их значение ... а не их идентификация (поскольку он никогда не будет присваивать им что-либо обратно). Однако было бы очень странно (например) иметь BeginInvoke()
вызов, который принимает int
и EndInvoke()
вызов, который принимает ref int
. Теперь, возможно, есть технические причины, по которым начальные / конечные вызовы должны иметь одинаковые подписи, но я думаю, что преимущества ясности и симметрии достаточны для проверки такой конструкции.
Все это, конечно, деталь реализации компилятора CLR и C # и может измениться в будущем. Интересно, однако, что существует вероятность путаницы - если вы ожидаете, что исходная переменная, переданная в BeginInvoke()
, будет фактически изменена. Это также подчеркивает важность вызова EndInvoke()
для завершения асинхронной операции.
Возможно, кто-то из команды C # (если они увидят этот вопрос) мог бы предложить более глубокое понимание деталей и вариантов дизайна, стоящих за этой функциональностью.