When T is collection, how to return of type collection. Currently it returns null
public static async Task<T> CallWebApi<T>(string url, string accessToken)
{
T result = default(T);
var response = await httpClient.GetAsync(url);
if (response.StatusCode == HttpStatusCode.NotFound)
{
return result; // returns null now. When T is collection, we want to return of
// type collection
}
}
CodePudding user response:
It's hard to provide a good default value for any possible T
. It's also obscure to the caller of the function what that default value might be.
A good pattern is thus to have the caller of the function provide that default value himself. That way the function can be used with any type T
without guessing what would be a good default value for any possible T
, and without causing surprises to callers.
public static async Task<T> CallWebApi<T>(string url, string accessToken, T defaultValue)
{
var response = await httpClient.GetAsync(url);
if (response.StatusCode == HttpStatusCode.NotFound)
{
return defaultValue;
}
// else
// get the content and deserialize to T,
// throw an exception if that doesn't work
}
The caller can then use it like:
var result = await CallWebApi<Model[]>(url, accessToken, Array.Empty<Model>());
Arguably, this is still all too complicated. What if the API returns an empty array? How would you know if that empty array is a default value or the actual API response?
With nullable reference types, a type-safe pattern that doesn't lose information that way is simply:
public static async Task<T?> CallWebApi<T>(string url, string accessToken) where T: class
{
var response = await httpClient.GetAsync(url);
if (response.StatusCode == HttpStatusCode.NotFound)
{
return null;
}
}
And callers cannot treat T?
as a value of T
without first checking if it's not null, which means they won't accidentally run into NullReferenceException
s or other such design problems.
CodePudding user response:
Personally I would provide default value factory parameter:
static async Task<T> CallWebApi<T>(string url, string accessToken, Func<T> defaultFactory = default)
{
var response = await new HttpClient().GetAsync(url);
if (response.StatusCode == HttpStatusCode.NotFound)
{
return defaultFactory is null
? default
: defaultFactory();
}
return default;
}
And let use write a little bit more wordy but clear code.
Otherwise you can try checking if type implements IEnumerable
(see this answer) and if it has parameterless ctor (see this answer) and exclude some "corner cases" like string
(which is IEnumerable<char>
) and decide what to do with collection interfaces (like IList<T>
- here you will need to decide what base collection would and make some reflection generic magic via Type.MakeGenericType()
).
CodePudding user response:
This:
T result = default(T);
Assigns null
, since the default of every reference type is null
. What you want to do is create an instance of the type. You can do that with Activator.CreateInstance
:
T result = Activator.CreateInstance<T>();
If T
was List<string>
, for example, that would be the equivalent to:
List<string> result = new List<string>();
Which creates an empty list.