我想写一个带out参数的async方法,像这样:

public async void Method1()
{
    int op;
    int result = await GetDataTaskAsync(out op);
}

我如何做到这一点在GetDataTaskAsync?


当前回答

下面是@dcastro回答的代码,为c# 7.0修改了命名元组和元组解构,简化了符号:

public async void Method1()
{
    // Version 1, named tuples:
    // just to show how it works
    /*
    var tuple = await GetDataTaskAsync();
    int op = tuple.paramOp;
    int result = tuple.paramResult;
    */

    // Version 2, tuple deconstruction:
    // much shorter, most elegant
    (int op, int result) = await GetDataTaskAsync();
}

public async Task<(int paramOp, int paramResult)> GetDataTaskAsync()
{
    //...
    return (1, 2);
}

有关新的命名元组、元组字面量和元组解构的详细信息,请参见: https://blogs.msdn.microsoft.com/dotnet/2017/03/09/new-features-in-c-7-0/

其他回答

对于真正想要保持参数的开发人员,这里可能有另一种解决方法。

将参数更改为数组或List以封装实际值。记得在发送到方法之前初始化列表。返回后,在使用它之前一定要检查值是否存在。小心编码。

在异步方法中不能有ref或out参数(如前所述)。

这需要在数据移动中进行一些建模:

public class Data
{
    public int Op {get; set;}
    public int Result {get; set;}
}

public async void Method1()
{
    Data data = await GetDataTaskAsync();
    // use data.Op and data.Result from here on
}

public async Task<Data> GetDataTaskAsync()
{
    var returnValue = new Data();
    // Fill up returnValue
    return returnValue;
}

您获得了更容易重用代码的能力,而且它比变量或元组更具可读性。

out形参的一个很好的特性是,即使函数抛出异常,也可以使用它们返回数据。我认为与异步方法最接近的等效方法是使用一个新对象来保存异步方法和调用者都可以引用的数据。另一种方法是在另一个答案中传递一个委托。

请注意,这两种技术都不具有编译器所具有的任何类型的强制。也就是说,编译器不会要求你在共享对象上设置值或调用传入委托。

下面是一个使用共享对象模拟ref和out的示例实现,用于async方法和其他各种ref和out不可用的场景:

class Ref<T>
{
    // Field rather than a property to support passing to functions
    // accepting `ref T` or `out T`.
    public T Value;
}

async Task OperationExampleAsync(Ref<int> successfulLoopsRef)
{
    var things = new[] { 0, 1, 2, };
    var i = 0;
    while (true)
    {
        // Fourth iteration will throw an exception, but we will still have
        // communicated data back to the caller via successfulLoopsRef.
        things[i] += i;
        successfulLoopsRef.Value++;
        i++;
    }
}

async Task UsageExample()
{
    var successCounterRef = new Ref<int>();
    // Note that it does not make sense to access successCounterRef
    // until OperationExampleAsync completes (either fails or succeeds)
    // because there’s no synchronization. Here, I think of passing
    // the variable as “temporarily giving ownership” of the referenced
    // object to OperationExampleAsync. Deciding on conventions is up to
    // you and belongs in documentation ^^.
    try
    {
        await OperationExampleAsync(successCounterRef);
    }
    finally
    {
        Console.WriteLine($"Had {successCounterRef.Value} successful loops.");
    }
}

我认为像这样使用ValueTuples是可行的。你必须先添加ValueTuple NuGet包:

public async void Method1()
{
    (int op, int result) tuple = await GetDataTaskAsync();
    int op = tuple.op;
    int result = tuple.result;
}

public async Task<(int op, int result)> GetDataTaskAsync()
{
    int x = 5;
    int y = 10;
    return (op: x, result: y):
}

这与Michael Gehling提供的答案非常相似,但我有自己的解决方案,直到我找到了他的解决方案,并注意到我不是第一个想到使用隐式转换的人。

无论如何,当nullable设置为启用时,我也想共享

public readonly struct TryResult<TOut>
{
    #region constructors

    public TryResult(bool success, TOut? value) => (Success, Value) = (success, value);

    #endregion

    #region properties

    public                                            bool  Success { get; init; }
    [MemberNotNullWhen(true, nameof(Success))] public TOut? Value   { get; init; }

    #endregion

    #region methods

    public static implicit operator bool(TryResult<TOut> result) => result.Success;
    public static implicit operator TryResult<TOut>(TOut value) => new (true, value);

    public void Deconstruct(out bool success, out TOut? value) => (success, value) = (Success, Value);

    public TryResult<TOut> Out([NotNullWhen(true)] out TOut? value)
    {
        value = Value;

        return this;
    }

    #endregion
}

然后你可以像这样写一个Try方法:

public static async Task<TryResult<byte[]>> TryGetBytesAsync(string file) =>
    File.Exists(file)
        ? await File.ReadAllBytesAsync(file)
        : default(TryResult<byte[]>);

像这样叫它:

if ((await TryGetBytesAsync(file)).Out(out var bytes))
    Console.WriteLine($"File has {bytes.Length} bytes.");