As for why async methods don't support out-by-reference parameters?
That's a limitation of the CLR. There are good reasons for that; the most obvious is that if you pass a local variable by reference, it is stored on the stack, but the current stack won’t remain available during the whole execution of the async method (only until the first
await
), so the location of the variable won’t exist anymore.
However, it’s pretty easy to work around that limitation : you only need to create a
Ref<T>
class to hold the value, and pass an instance of this class by value to the async method:public
class
Ref<T>
{
public
Ref() { }
public
Ref(T value) { Value = value; }
public
T Value {
get
;
set
; }
public
override
string
ToString()
{
T value = Value;
return
value ==
null
?
""
: value.ToString();
}
public
static
implicit
operator
T(Ref<T> r) {
return
r.Value; }
public
static
implicit
operator
Ref<T>(T value) {
return
new
Ref<T>(value); }
}
async
void
Button_Click(
object
sender, EventArgs e)
{
var
count =
new
Ref<
int
>();
var
size =
new
Ref<
ulong
>();
await
GetFileStats(tbPath.Text, count, size);
txtFileStats.Text =
string
.Format(
"{0} files ({1} bytes)"
, count, size);
}
async
Task GetFileStats(
string
path, Ref<
int
> totalCount, Ref<
ulong
> totalSize)
{
var
folder =
await
StorageFolder.GetFolderFromPathAsync(path);
foreach
(
var
f
in
await
folder.GetFilesAsync())
{
totalCount.Value
+= 1;
var
props =
await
f.GetBasicPropertiesAsync();
totalSize.Value += props.Size;
}
foreach
(
var
f
in
await
folder.GetFoldersAsync())
{
await
GetFilesCountAndSize(f, totalCount, totalSize);
}
}
As you can see, it’s pretty straightforward. This approach can also be used in iterator blocks (i.e. yield return
), that also don’t allow ref
and out
parameters. It also has an advantage over standard ref
and out
parameters: you can make the parameter optional, if for instance you’re not interested in the result (obviously, the callee must handle that case appropriately).