1

I have a generic service like below:

public interface ISyncedDataService<T> where T : BaseDBModel
    {
        Task<int> SaveItem(T item);
        Task<int> InsertAllItems(List<T> items);
        Task<List<T>> GetItems();
        Task<T> GetItem(long primaryKey);
        Task<int> DeleteItem(T item);
    }

And have a bunch of classes T1, T2, T3, T4.. T20 which are inherited from BaseDBModel.

I am using Autofac for invoking services like below:

using var scope = App.Container.BeginLifetimeScope(); //Container is Autofac.IContainer

var items = await scope.Resolve<ISyncedDataService<T1>>().GetItems(); //T1 inherits BaseDBModel

How do I invoke the GetItems() if the type T1...T20 is known only on run time? Currently I am using an if-else chain to make it simple and fast. But curios on how I can do that dynamically (reflections?)

Edit: When using reflections, the challenge is when trying to dynamically get the MethodInfo for an extension method which is also a generic method.

Abhijith C R
  • 1,440
  • 1
  • 12
  • 12
  • Not important, but would be good to know how the types are registered. Is it required you need a nested scope here? (not that it makes a difference to your question, just knowing). – nawfal Oct 18 '21 at 15:32
  • builder.RegisterInstance(GetService(DependencyService.Get>())).AsImplementedInterfaces().SingleInstance(); //doing this for all T1.. T20. And the services are registered as [assembly: Xamarin.Forms.Dependency(typeof(Project.Droid.Services.SyncedDataService))] [assembly: Xamarin.Forms.Dependency(typeof(Project.Droid.Services.SyncedDataService))] namespace Project.Droid.Services { public class SyncedDataService : Services.SyncedDataService where T : Models.BaseDBModel, new() { } } //That is for Android, does same in iOS too – Abhijith C R Oct 22 '21 at 12:22
  • Is it important to register an instance? If so, how is `DependencyService.Get>` resolving the instance for you? Is this an external DI service? – nawfal Oct 22 '21 at 20:43

3 Answers3

1

You cannot resolve type argument in a generic class using reflection.

But to invoke a method in a specific class, this answer which uses reflection might help.

How do I use reflection to call a generic method?

If you just want to register a generic interface as a generic class.

  `builder.RegisterGeneric(typeof(YourGenericClass<>)).As(typeof(YourGenericInterface<>))`

is the code using autofac.

Jins Peter
  • 2,368
  • 1
  • 18
  • 37
  • As you might have missed to notice, the generic .Resolve<>() in Autofac is an extension method (IComponentContext.Resolve<>), and the above solution is not applicable in this case. – Abhijith C R Oct 18 '21 at 13:21
  • To simplify the question, how do I invoke a generic method using a generic type as its type argument? The object 'scope' in the above code gets .Resolve<>() method from the extension IComponentContext. Question is not precisely about invoking a generic type, but more focused towards invoking a generic method . – Abhijith C R Oct 18 '21 at 14:00
  • 1
    see the answer in the link I gave – Jins Peter Oct 18 '21 at 14:18
1

If you dont have a bound generic type with you at runtime, then you run into this if-else type checking pattern. I am not sure if this has got much to do with Autofac. At the risk of sounding obvious, you could resolve this calling the non-generic Resolve method which returns an object.

object foo = componentContext.Resolve(type);

This is as generic as it gets.

An additional thing you can do is cast the object back to ISyncedDataService<BaseDBModel> but this only works if your interface is covariant. Declare it like:

interface ISyncedDataService<out T> where T : BaseDBModel

// and then
var foo = (ISyncedDataService<BaseDBModel>)componentContext.Resolve(type);

Again, whether covariance makes sense for your interface is something you have to decide.


Some caveats.. Hope you are not using the service locator anti-pattern here. Also take care of the lifetime of scope there, you seem to dispose the scope in the method you resolve the dependency. That should by default terminate the dependency along with the scope. Hope the dependencies are registered to be longer lived.

nawfal
  • 70,104
  • 56
  • 326
  • 368
0

I solved it like below:

var dbType = typeof(ISyncedDataService<>).MakeGenericType(runtTimeType);
var genericService = scope.Resolve(dbType);
MethodInfo methodInfo = genericService.GetType().GetMethod("GetItems");
var task = (Task)methodInfo.Invoke(genericService, null);
await task.ConfigureAwait(false);
var resultProperty = task.GetType().GetProperty("Result");
var items = resultProperty.GetValue(task);

The result also had to be resolved from the task through reflection.

Abhijith C R
  • 1,440
  • 1
  • 12
  • 12
  • 1. You could use `dynamic` keyword to make these reflection calls simpler. E.g. `dynamic genericService = scope.Resolve(dbType); Task task = genericService.GetItems();` and so on. Same with .Result property. 2. the bigger question is, how do you proceed with the final `items`? you still only have got an object. 3. These reflection calls get expensive if called in a loop. Your code smells like service locator pattern. I hope your `scope.Resolve` doesn't come in user code/core layers. – nawfal Oct 22 '21 at 20:41
  • These items are further consumed by a generic method which saves them into an offline copy of the DB. This entire routine is for dynamically syncing up updated tables in the server into an offline copy in the mobile device. – Abhijith C R Nov 08 '21 at 11:49
  • And yes, I use service locator pattern very widely in the app. There are some simpler overrides that I use to make performance hits negligible. But this pattern has helped me to maintain a decorum and sanity in multiple scenarios. – Abhijith C R Nov 08 '21 at 11:52