Home > Software engineering >  cannot convert type T to PlayFabResult<T>
cannot convert type T to PlayFabResult<T>

Time:12-14

I am using the Unity PlayFab SDK and wanting to convert the provided async callback style API to use async/await to make the code more readable. I am trying to write a generic method to wrap any arbitrary API call into a Task. I am very close to getting this working, but can't figure out how to set the error property on the error callback.

Here's my example:

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);
            //THIS LINE GIVES ERROR - WORKS IF I COMMENT IT OUT BUT THEN ERRORS ARE SWALLOWED
            ((PlayFabResult<TOut>)ret).Error = error;
            tcs.SetResult(ret);
        }

        playfabMethod.Invoke( request, resultCallback, errorCallback, customData, extraHeaders );
 
        return tcs.Task;
    }
}

This (almost) works great like this:

    var confirm = await PFAsyncWorks.Run<ConfirmPurchaseRequest, ConfirmPurchaseResult>(
                 PlayFabClientAPI.ConfirmPurchase, new ConfirmPurchaseRequest() {OrderId = "sd"} );  

This works great, now I can call any of the PlayFab APIs (which all follow the same paradigm of request, PlayFabResponse. But I want to be able to handle the error callback properly. Does anyone know how I can cast the TOut object like I am trying to above so I can set the Error property?

I get the error "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'.

I have tried adding 'Where', but I still get issues

    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);// TrySetResult(result);
            }

            void errorCallback(PlayFabError error)
            {
                Debug.LogError(error.GenerateErrorReport());
                
                TOut t = new TOut();
                
                t.Error = error;
                
                tcs.TrySetResult(t);//new TOut { Error = error });
            }

            playfabMethod.Invoke(request, resultCallback, errorCallback, customData, extraHeaders);
        }
        catch(Exception ex)
        {
            //tcs.TrySetException(new Exception(error.GenerateErrorReport())
            tcs.TrySetException(ex);
        }
        
        return tcs.Task;
    }
}

This version gives the error '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'

Thanks for any help!

CodePudding user response:

Well you do

where TOut : PlayFabResult<TOut>

if you think about it, it makes no sense since this basically means e.g. (I know the types make no sense - it is for clarification only)

where int : PlayFabResult<int>

=> What you want here is to limit the TOut to PlayFabResult<TResult> and then further limit that TResult to PlayFabResultCommon.

You might rather need to wrap this further in another generic and do e.g.

public static Task<TOut> Run<TIn, TOut, TResult>(PlayFabMethod<TIn,TOut> playfabMethod, TIn request, object customData = null, Dictionary<string, string> extraHeaders = null) 
    where TIn : PlayFabRequestCommon 
    where TOut : PlayFabResult<TResult>, new()
    where TResult : PlayFabResultCommon

However, I just imported the package and there are more issues with your approach:

  1. There simply is no property/field Error in PlayFabReslt<T>

  2. Not all the playfab results actually inherit from PlayfabResult<TResult>!

    E.g. LoginResult directly inherits from PlayFabResultCommon, skipping the PlayfabResult<TResult>.

    Actually I didn't find ANY whatsoever ^^

For 2) you will need a different overload which actually kinda works like your first approach with only two generic types for the request and the result.

For 1) (and finally combining it all) I would simply introduce a wrapper type around the results like e.g.

public abstract class PlayFabResultWithError
{
    public PlayFabError Error { get; }

    protected PlayFabResultWithError(PlayFabError error)
    {
        Error = error;
    }
}

// This one is for the PlayFabResult<TResult> values
public class PlayFabResultWithError<TResult, TResultInner> : PlayFabResultWithError 
    where TResult : PlayFabResult<TResultInner> 
    where TResultInner : PlayFabResultCommon
{
    public TResult Result { get; }
    
    public PlayFabResultWithError(TResult result) : base(null)
    {
        Result = result;
    }
    
    public PlayFabResultWithError(PlayFabError error) : base(error) { }
}

// This one is for all the more generic PlayFabResultCommon values
public class PlayFabResultWithError<TResult> : PlayFabResultWithError 
    where TResult : PlayFabResultCommon
{
    public TResult Result { get; }

    public PlayFabResultWithError(TResult result) : base(null)
    {
        Result = result;
    }

    public PlayFabResultWithError(PlayFabError error) : base(error) { }
}

and then change your methods to

public delegate void PlayFabMethod<in TIn, out TResult>(TIn r, Action<TResult> o, Action<PlayFabError> e, object c = null, Dictionary<string, string> h = null);

// This one handles PlayFabResult<TResult>
public static async Task<PlayFabResultWithError<TOut, TResult>> Run<TIn, TOut, TResult>(PlayFabMethod<TIn, TOut> playfabMethod, TIn request, object customData = null, Dictionary<string, string> extraHeaders = null)
    where TIn : PlayFabRequestCommon
    where TResult : PlayFabResultCommon
    where TOut : PlayFabResult<TResult>
{
    var tcs = new TaskCompletionSource<PlayFabResultWithError<TOut, TResult>>();

    try
    {
        void resultCallback(TOut result)
        {
            tcs.TrySetResult(new PlayFabResultWithError<TOut, TResult>(result));
        }

        void errorCallback(PlayFabError error)
        {
            Debug.LogError(error.GenerateErrorReport());

            tcs.TrySetResult(new PlayFabResultWithError<TOut, TResult>(error));
        }

        playfabMethod.Invoke(request, resultCallback, errorCallback, customData, extraHeaders);
    }
    catch (Exception ex)
    {
        tcs.TrySetException(ex);
    }

    return await tcs.Task;
}

// This one handles more generic PlayFabResultCommon
public static async Task<PlayFabResultWithError<TResult>> Run<TIn, TResult>(PlayFabMethod<TIn, TResult> playfabMethod, TIn request, object customData = null, Dictionary<string, string> extraHeaders = null)
    where TIn : PlayFabRequestCommon
    where TResult : PlayFabResultCommon
{
    var tcs = new TaskCompletionSource<PlayFabResultWithError<TResult>>();

    try
    {
        void resultCallback(TResult result)
        {
            tcs.TrySetResult(new PlayFabResultWithError<TResult>(result));
        }

        void errorCallback(PlayFabError error)
        {
            Debug.LogError(error.GenerateErrorReport());

            tcs.TrySetResult(new PlayFabResultWithError<TResult>(error));
        }

        playfabMethod.Invoke(request, resultCallback, errorCallback, customData, extraHeaders);
    }
    catch (Exception ex)
    {
        tcs.TrySetException(ex);
    }

    return await tcs.Task;
}

Now you can do e.g.

var req = new InsightsEmptyRequest();
var details = await Example.Run<InsightsEmptyRequest, InsightsGetDetailsResponse>(PlayFabInsightsAPI.GetDetails, req);

var req2 = new LoginWithEmailAddressRequest();
var login = await Example.Run<LoginWithEmailAddressRequest, LoginResult>(PlayFabClientAPI.LoginWithEmailAddress, req2);
  • Related