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
Resultconversion fails, e.g. you are specifying an integer type forResultbut the value is"hi". The implementation above will throw an exception from withinConvert.ChangeType()if this happens.You also don't specify what should happen when the
Resultcorresponds to some complex object that does not implementIConvertible. 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) useChangeType(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.
