I've been experimenting with generics in C# to improve writing boilerplate code.
Recently, I wrote this method to repeat a paginated REST API call until the pages ran out. The method assumes the object obeys an IPaginated
interface, which just has a required ID property.
This was my original implementation.
/// <summary>
/// Generic implementation of getting all object from a paginated API call.
/// </summary>
/// <param name="getTask">Delegate to the API calling method, points to the last object ID or null.</param>
/// <param name="limit">The limit on # of elements.</param>
/// <returns></returns>
internal static async Task<Models.IPaginated[]> GetPaginated(Func<string, Task<Models.IPaginated[]>> getTask, int limit)
{
List<Models.IPaginated> values = new List<Models.IPaginated>();
Models.IPaginated[] page = await getTask(null);
values.AddRange(page);
while (page.Length == limit)
{
var lastobj = page[limit - 1];
page = await getTask(lastobj.ID);
values.AddRange(page);
}
return values.ToArray();
}
However, that cause a compiler error when I wrote out the delegate object. (Class A implements IPaginated.)
public async Task<Models.ClassA[]> GetAll(string arg1, string arg2)
{
int limit = 100;
Func<string, Task<Models.ClassA[]>> getTask = ((string lastobj) =>
{
return Get(arg1, arg2, limit, lastobj);
});
return await GetPaginated(getTask, limit);
}
So, now I reworked the method to be a generic conditioned by IPaginated
.
/// <summary>
/// Generic implementation of getting all object from a paginated API call.
/// </summary>
/// <param name="getTask">Delegate to the API calling method, points to the last object ID or null.</param>
/// <param name="limit">The limit on # of elements.</param>
/// <returns></returns>
internal static async Task<T[]> GetPaginated<T>(Func<string, Task<T[]>> getTask, int limit)
where T : Models.IPaginated
{
List<T> values = new List<T>();
T[] page = await getTask(null);
values.AddRange(page);
while (page.Length == limit)
{
var lastobj = page[limit - 1];
page = await getTask(lastobj.ID);
values.AddRange(page);
}
return values.ToArray();
}
which causes no compiler errors. But why? And how does T : IPaginated allow me to retrieve the .ID property without casting the object?