Unity PlayFab SDK异步转async/await时PlayFabResult类型转换错误
使用Unity PlayFab SDK时,希望将其异步回调风格的API转换为async/await用法以提升代码可读性,编写了泛型方法包装任意API调用为Task,但在错误回调中无法正确设置Error属性,遇到编译错误。
初始实现代码
public static class PFAsyncWorks { public delegate void PlayFabMethod<TIn, TOut>( TIn r, Action<TOut> o , Action<PlayFabError> e, object c, Dictionary<string, string> h); public static Task<TOut> Run<TIn, TOut>( PlayFabMethod<TIn,TOut> playfabMethod, TIn request, object customData = null, Dictionary<string, string> extraHeaders = null ) { var tcs = new TaskCompletionSource<TOut>(); Action<TOut> resultCallback = result => tcs.SetResult( result ); void errorCallback(PlayFabError error) { var ret = default(TOut); // 此行报错,注释后可运行但错误会被吞掉 ((PlayFabResult<TOut>)ret).Error = error; tcs.SetResult(ret); } playfabMethod.Invoke( request, resultCallback, errorCallback, customData, extraHeaders ); return tcs.Task; } }
调用代码
var confirm = await PFAsyncWorks.Run<ConfirmPurchaseRequest, ConfirmPurchaseResult>( PlayFabClientAPI.ConfirmPurchase, new ConfirmPurchaseRequest() {OrderId = "sd" });
遇到的错误
The type 'TOut' cannot be used as type parameter 'TResult' in the generic type or method 'PlayFabResult'. There is no implicit reference conversion from 'TOut' to 'PlayFab.SharedModels.PlayFabResultCommon'.
尝试添加泛型约束后的代码
public static Task<TOut> Run<TIn, TOut>( PlayFabMethod<TIn,TOut> playfabMethod, TIn request, object customData = null, Dictionary<string, string> extraHeaders = null ) where TIn : PlayFabRequestCommon where TOut : PlayFabResult<TOut>, new() { var tcs = new TaskCompletionSource<TOut>(); try { void resultCallback(TOut result) { tcs.TrySetResult(result); } void errorCallback(PlayFabError error) { Debug.LogError(error.GenerateErrorReport()); TOut t = new TOut(); t.Error = error; tcs.TrySetResult(t); } playfabMethod.Invoke(request, resultCallback, errorCallback, customData, extraHeaders); } catch(Exception ex) { tcs.TrySetException(ex); } return tcs.Task; }
此版本仍报错:
The type 'TOut' cannot be used as type parameter 'TResult' in the generic type or method 'PlayFabResult'. There is no implicit reference conversion from 'TOut' to 'PlayFab.SharedModels.PlayFabResultCommon'.
解决方案
1. 修正泛型约束
问题出在错误的泛型约束上:PlayFab的所有具体结果类(如ConfirmPurchaseResult)都继承自PlayFabResultCommon,而非PlayFabResult<TOut>。PlayFabResult<T>是通用包装类,PlayFabResultCommon才是所有结果类型的基类,且包含Error属性。
修改后的泛型约束如下:
public static Task<TOut> Run<TIn, TOut>(PlayFabMethod<TIn, TOut> playfabMethod, TIn request, object customData = null, Dictionary<string, string> extraHeaders = null) where TIn : PlayFabRequestCommon where TOut : PlayFabResultCommon, new() // 正确的约束
2. 正确设置Error属性
修正约束后,即可直接在错误回调中创建TOut实例并设置Error属性:
void errorCallback(PlayFabError error) { Debug.LogError(error.GenerateErrorReport()); var errorResult = new TOut(); errorResult.Error = error; // 现在可正常访问Error属性 tcs.TrySetResult(errorResult); }
3. 更符合async/await规范的错误处理(推荐)
如果希望遵循C#异步编程最佳实践,建议在错误回调中直接设置Task的异常,而非返回带Error的结果对象。调用方可以通过try/catch捕获错误,逻辑更清晰:
修改错误回调:
void errorCallback(PlayFabError error) { // 将PlayFabError包装为Exception,保留原始错误信息 tcs.TrySetException(new Exception(error.GenerateErrorReport(), error)); }
调用时使用try/catch处理错误:
try { var confirm = await PFAsyncWorks.Run<ConfirmPurchaseRequest, ConfirmPurchaseResult>( PlayFabClientAPI.ConfirmPurchase, new ConfirmPurchaseRequest() { OrderId = "sd" }); // 处理成功结果 } catch (Exception ex) { // 取出原始PlayFabError var playFabError = ex.InnerException as PlayFabError; if (playFabError != null) { Debug.LogError(playFabError.GenerateErrorReport()); // 针对不同错误类型做处理 } }
内容的提问来源于stack exchange,提问作者user3010678

