如何使用异步实现TryDoSomething模式

Luk*_*ett 4 c# design-patterns asynchronous async-await c#-5.0

我经常使用TryDoSomething完全像这样的模式:

GameContext gameContext;
if (gamesRepository.TryLoadLastGame(out gameContext))
{
    // Perform actions with gameContext instance.
}
else
{
    // Create a new game or go to home screen or whatever.
}
Run Code Online (Sandbox Code Playgroud)

这允许良好的可读流程,但也允许成功状态,true但返回空值,该值有时对于传达“我能够得到你的东西但实际上为空”很有用。

使用async-await,异步低级API“强制”调用API以执行正确的操作并异步工作。但是,没有out参数,此模式将不起作用。

如何实现?

我有一个答案,并且正在回答问/答样式,以了解其他人对此的看法。

Luk*_*ett 5

到目前为止,我已经使用了一个小类Attempt<T>

public sealed class Attempt<T>
{
    /// <summary>
    /// Initializes a new instance of the <see cref="Attempt{T}"/> class.
    /// </summary>
    public Attempt() { }

    /// <summary>
    /// Initializes a new instance of the <see cref="Attempt{T}"/> class.
    /// </summary>
    public Attempt(Exception exception)
    {
        this.Exception = exception;
    }

    /// <summary>
    /// Initializes a new instance of the <see cref="Attempt{T}"/> class.
    /// </summary>
    /// <param name="result">The result.</param>
    public Attempt(T result)
    {
        this.Result = result;
        this.HasResult = true;
    }

    /// <summary>
    /// Gets the result.
    /// </summary>
    /// <value>The result.</value>
    public T Result { get; private set; }

    /// <summary>
    /// Determines whether this instance has result.
    /// </summary>
    /// <returns><c>true</c> if this instance has result; otherwise, <c>false</c>.</returns>
    public bool HasResult { get; private set; }

    /// <summary>
    /// Returns the result with a true or false depending on whether its empty, but throws if there is an exception in the attempt.
    /// </summary>
    /// <param name="result">The result, which may be null.</param>
    /// <returns><c>true</c> if the specified result has a value; otherwise, <c>false</c>.</returns>
    /// <exception cref="System.AggregateException">The attempt resulted in an exception. See InnerExceptions.</exception>
    public bool TryResult(out T result)
    {
        if (this.HasResult)
        {
            result = this.Result;
            return true;
        }
        else
        {
            if (this.Exception != null)
            {
                throw new AggregateException("The attempt resulted in an exception. See InnerExceptions.", this.Exception);
            }
            else
            {
                result = default(T);
                return false;
            }
        }
    }

    /// <summary>
    /// Gets or sets the exception.
    /// </summary>
    /// <value>The exception.</value>
    public Exception Exception { get; private set; }
}
Run Code Online (Sandbox Code Playgroud)

在像这样的Try方法中使用它:

internal async Task<Attempt<T>> TryReadObjectAsync<T>(string folderName, string fileName)
{
    if (String.IsNullOrWhiteSpace(folderName))
        throw new ArgumentException("The string argument is null or whitespace.", "folderName");

    if (String.IsNullOrWhiteSpace(fileName))
        throw new ArgumentException("The string argument is null or whitespace.", "fileName");

    try
    {
        StorageFolder folder = this.StorageFolder;                
        if (folderName != @"\")
            folder = await StorageFolder.GetFolderAsync(folderName);

        var file = await folder.GetFileAsync(fileName);
        var buffy = await FileIO.ReadBufferAsync(file);

        string xml = await buffy.ReadUTF8Async();

        T obj = await Evoq.Serialization.DataContractSerializerHelper.DeserializeAsync<T>(xml);

        return new Attempt<T>(obj);
    }
    catch (FileNotFoundException fnfe)
    {
        // Only catch and wrap expected exceptions.

        return new Attempt<T>(fnfe);
    }
}
Run Code Online (Sandbox Code Playgroud)

并且像这样被消耗:

DataContractStorageSerializer xmlStorage = new DataContractStorageSerializer(this.StorageFolder);

var readAttempt = await xmlStorage.TryReadObjectAsync<UserProfile>(userName, UserProfileFilename);

try
{
    UserProfile user;
    if (readAttempt.TryResult(out user))
    {
        // Do something with user.
    }
    else
    {
        // No user in the persisted file.
    }
}
catch (Exception ex)
{
    // Some unexpected, unhandled exception happened.
}
Run Code Online (Sandbox Code Playgroud)

或者,忽略错误。

...
var readAttempt = await xmlStorage.TryReadObjectAsync<UserProfile>(userName, UserProfileFilename);

if (readAttempt.HasResult)
{
    // Continue.

    this.DoSomethingWith(readAttempt.Result);
}
else
{
    // Create new user.
}
Run Code Online (Sandbox Code Playgroud)

  • 您可能需要改进这一部分:`throw this.Exception`。改为执行以下操作:`throw new AggregateException(“ TryResult”,this.Exception)`。否则,您将无法从引发原始异常的位置访问调试信息(如堆栈框架)。 (3认同)