Home > Software engineering >  How to reconstruct the return shape of a generic method which has generic return type?
How to reconstruct the return shape of a generic method which has generic return type?

Time:09-08

I have a generic method called Transfer which returns a generic type too. In the body of Transfer, I do some processing and get MessageBundle object. How Can I reconstruct the return type of Transfer (using MessageBundle object) in a generic way?

I can only make it work in non-generic way.

namespace ConsoleApp
{
    internal class Program
    {
        static void Main(string[] args)
        {
            //this works
            var test = Transfer<(string? Result, string? Error)>();
            
            //???
            var test = Transfer<(int? Result, string? Error)>();
            var test = Transfer<(bool? Result, string? Error)>();
            var test = Transfer<(object? Result, string? Error)>();
        }

        public static U Transfer<U>()
        {
            //getting MessageBundle object
            var bundle = new MessageBundle
            {
                Result = "hi",
                Error = null
            };

            //How to reconstruct in generic way?
            (string? Result, string? Error) response = (bundle.Result as string, bundle.Error);

            return (U)Convert.ChangeType(response, typeof(U));
        }
    }

    public class MessageBundle
    {
        public string? Error { get; set; }
        public object? Result { get; set; }
    }
}

Edit 1: I have no control over the signature of Transfer method.

Edit 2: The shape of U (Transfer method) will be a tuple of two elements (Error is always a string while Result can vary)

CodePudding user response:

If you know in advance that U will be a tuple of 2 items but cannot change the signature of public static U Transfer<U>() in any way (even by adding an ITuple constraint), you will need to use reflection to construct the tuple. And assuming that the first tuple parameter is a .NET primitive of some sort and that the value of Result is its string representation, you can use Convert.ChangeType() to convert the string value to the final value.

One way to do this would be:

if (!typeof(ITuple).IsAssignableFrom(typeof(U))  // Assert it's a tuple
    || !(typeof(U).IsGenericType && typeof(U).GetGenericArguments() is var arguments && arguments.Length == 2) // Assert it has exactly two generic parameters
    || arguments[1] != typeof(string)) // Assert that the second parameter is a string
    throw new ArgumentException(string.Format("Unexpected type {0}", typeof(U)));

var tuple = (U)Activator.CreateInstance(typeof(U), 
                                        bundle.Result == null ? null : Convert.ChangeType(bundle.Result, Nullable.GetUnderlyingType(arguments[0]) ?? arguments[0]), 
                                        bundle.Error)!;

Notes:

  • You don't specify what should happen in the event the Result conversion fails, e.g. you are specifying an integer type for Result but the value is "hi". The implementation above will throw an exception from within Convert.ChangeType() if this happens.

  • You also don't specify what should happen when the Result corresponds to some complex object that does not implement IConvertible. The implementation above will also throw an exception in such a case.

  • Convert.ChangeType(object, Type) uses the current culture to do the conversion. If you want to use the invariant culture (e.g. because you are deserializing a value received over the wire) use ChangeType(Object, Type, CultureInfo.InvariantCulture):

      var tuple = (U)Activator.CreateInstance(typeof(U), 
                                              bundle.Result == null ? null : Convert.ChangeType(bundle.Result, Nullable.GetUnderlyingType(arguments[0]) ?? arguments[0], CultureInfo.InvariantCulture), 
                                              bundle.Error)!;
    
  • You really should rethink this design. Using reflection in this manner eliminates almost all compile-time checking for code correctness.

Demo fiddle here.

  • Related