C
C#15mo ago
Vitor Durante

❔ How to cast to a generic of object?

I am using an external api that returns dynamic data. It could be a string, a list or a table. In my code, I am building a controller endpoint that will read the data from this API, parse the data from the body, and return as a result to the requester. To help me with that, I have attempted to build a generic ApiResult<T> that will contain the parsed data. The issue is that I am attempting to cast the ApiResult<T> to an ApiResult<object> and unfortunately it doesn't allow me to do that since object and T doesn't seem to be compatible. Could anyone help me? I don't need the result to be strongly typed, but I really wanted to have the Parse methods strongly typed to have intellisense.
public class ApiResult<T> where T : class
{
public bool Success { get; set; }
public T Data { get; set; }
}

public class ApiResult : ApiResult<object>
{
}

public static class HttpResponseExtensions
{
public static ApiResult ParseApiResult(this HttpResponse response)
{
// I cannot do this because ApiResult<object> is not compatible with ApiResult<T>
return response.Headers["Structure"] switch
{
"text" => response.ParseString(),
"list" => response.ParseList(),
"table" => response.ParseTable(),
_ => null
};
}

private static ApiResult<List<string>> ParseList(this HttpResponse response)
{
return new ApiResult<List<string>>();
}

private static ApiResult<string> ParseString(this HttpResponse response)
{
return new ApiResult<string>();
}

private static ApiResult<List<List<string>>> ParseTable(this HttpResponse response)
{
return new ApiResult<List<List<string>>>();
}
}
public class ApiResult<T> where T : class
{
public bool Success { get; set; }
public T Data { get; set; }
}

public class ApiResult : ApiResult<object>
{
}

public static class HttpResponseExtensions
{
public static ApiResult ParseApiResult(this HttpResponse response)
{
// I cannot do this because ApiResult<object> is not compatible with ApiResult<T>
return response.Headers["Structure"] switch
{
"text" => response.ParseString(),
"list" => response.ParseList(),
"table" => response.ParseTable(),
_ => null
};
}

private static ApiResult<List<string>> ParseList(this HttpResponse response)
{
return new ApiResult<List<string>>();
}

private static ApiResult<string> ParseString(this HttpResponse response)
{
return new ApiResult<string>();
}

private static ApiResult<List<List<string>>> ParseTable(this HttpResponse response)
{
return new ApiResult<List<List<string>>>();
}
}
4 Replies
atakancracker
atakancracker15mo ago
Why do you need to cast ApiResult<T> to an ApiResult<object> ? Once you for example call ParseList method, it returns ApiResult<List<string>> which is already strongly typed
Vitor Durante
Vitor Durante15mo ago
@Atakan / Cracker because I am gonna call ParseApiResult from a method that knows nothing about the underlying types. So it needs to get a generic ApiResult as a result
JakenVeina
JakenVeina15mo ago
so, if you have, say
ApiResult<List<string>> someResult = ...
ApiResult<List<string>> someResult = ...
and you want to cast it to ApiResult<object>? the only way that is possible is with polymorphic covariance C# has this built-in for certain common types like IEnumerable<T>
IEnumerable<List<string>> someSequence = ...

IEnumerable<object> sameSequence = someSequence;
IEnumerable<List<string>> someSequence = ...

IEnumerable<object> sameSequence = someSequence;
IEnumerable<T> is defined as....
public interface IEnumerable<out T>
: IEnumerable
{
IEnumerator<T> GetEnumerator();
}
public interface IEnumerable<out T>
: IEnumerable
{
IEnumerator<T> GetEnumerator();
}
the key is the out modifier on T this is only possible within an interface you could not add this to your ApiResult<T> class you'd have to define an IApiResult<T> interface to be used for passing these things around MVC does this, for example alternatively if your goal is to write the ParseApiResult() method you describe, you could do something like this
public abstract class ApiResult
{
public bool Success { get; set; }

public abstract object GetData();
}

public class ApiResult<T>
: ApiResult
{
public T Data { get; set; }

public override object GetData()
=> Data;
}
public abstract class ApiResult
{
public bool Success { get; set; }

public abstract object GetData();
}

public class ApiResult<T>
: ApiResult
{
public T Data { get; set; }

public override object GetData()
=> Data;
}
not ideal, but doable alternatively alternatively, you'd need to use some reflection in your parse method
public void ParseApiResult(object result)
{
var resultType = result.GetType();
if (!resultType.IsGenericType
|| (resultType.GetGenericTypeDefinition() != typeof(ApiResult<>)))
throw new ArgumentException($"Cannot parse result of type {result.GetType().FullName}");

var parseGenericMethodInfo = // Reflection, to retrieve method info for ParseApiResult<T>()

parseGenericMethodInfo.Invoke(this, new object[] { result });
}

private void ParseApiResult<T>(ApiResult<T> result)
{
...
}
public void ParseApiResult(object result)
{
var resultType = result.GetType();
if (!resultType.IsGenericType
|| (resultType.GetGenericTypeDefinition() != typeof(ApiResult<>)))
throw new ArgumentException($"Cannot parse result of type {result.GetType().FullName}");

var parseGenericMethodInfo = // Reflection, to retrieve method info for ParseApiResult<T>()

parseGenericMethodInfo.Invoke(this, new object[] { result });
}

private void ParseApiResult<T>(ApiResult<T> result)
{
...
}
there MIGHT be an even better solution than any of these, but I'd need to know more context about what you're trying to achieve, overall, with this ApiResult<T> type
Accord
Accord15mo ago
Was this issue resolved? If so, run /close - otherwise I will mark this as stale and this post will be archived until there is new activity.