Generic interface argument from string

Viewed 45

I am trying to add generics to my DAL. There's a list of EF entities and their corresponding repositories. These repositories implement a generic interface. I can't figure out how to instantiantiate the repository.

      public T Create(T dtoEntity)
        {
            string entityClassName = dtoEntity.GetType().Name;
            string repositoryClassName = entityClassName + "Repository";

            try
            {
                string entityFullName = entitiesNamespace + entityClassName;
                IEntityBase entity = (IEntityBase)assembly.CreateInstance(entityFullName)!;
                string repositoryFullName = repositoryNamespace + repositoryClassName;
                Type myType = Type.GetType("SmartVisionERP.Dal.SqlServer.Master.DataModel.Config_Accounts,SmartVisionERP.Dal.SqlServer.Master")!;
                // IEntityBaseRepository<myType> repository = (IEntityBaseRepository<myType>)assembly.CreateInstance(repositoryFullName)!
                IEntityBaseRepository<Config_Accounts> repository = (IEntityBaseRepository<Config_Accounts>)assembly.CreateInstance(repositoryFullName)!;
                var list = repository.GetList();
            }
            catch (Exception)
            {
                throw;
            }

            return dtoEntity;
        }
  1. I am receiving a dtoEntity, I'm extracting the class name and then build the repository name out of it. For the scenario I am testing, these are "Config_Accounts" and "Config_AccountsRepository".
  2. I am using reflection to instatiate the EF entity, has the same name, it's located in a different assembly. This line works properly, I have the entity.
  3. The repository interface expects a T. IEntityBaseRepository<T> where T : class, IEntityBase, new()
  4. I am getting the correct type in myType variable.
  5. The commented line fails with the message "myType is a variable but is used as a type".
  6. As soon as I write Config_Accounts instead of myType, it works, but this defeats the goal, I am trying to pass the actual type there.

I am out of ideas. Could anyone shed some light? How can I pass to that line a type generated from a string, in such way it actually works?

Thank you

========= EDIT =========

Based on help I received I have changed the code to look like below. I got an error stating "cannot instantiate an interface", which makes sense, so I passed the base class instead. I got the repository in an object, but the object does not expose any of the methods defined in the base class. I am assuming those will need to be exposed and used through more reflection, as suggested in one of the answers.

      public T Create(T dtoEntity)
        {
            string entityClassName = dtoEntity.GetType().Name;
            string repositoryClassName = entityClassName + "Repository";

            try
            {
                string entityFullName = $"{entitiesNamespace}{entityClassName}";
                IEntityBase entity = (IEntityBase)assembly.CreateInstance(entityFullName)!;
                Type template = typeof(EntityBaseRepository<>);
                Type myType = Type.GetType("SmartVisionERP.Dal.SqlServer.Master.DataModel.Config_Accounts,SmartVisionERP.Dal.SqlServer.Master")!;
                Type genericType = template.MakeGenericType(myType);
                object instance = Activator.CreateInstance(genericType);
                
                //string repositoryFullName = repositoryNamespace + repositoryClassName;
                //IEntityBaseRepository<Config_Accounts> repository = (IEntityBaseRepository<Config_Accounts>)assembly.CreateInstance(repositoryFullName)!;
                //var list = repository.GetList();
            }
            catch (Exception)
            {
                throw;
            }

            return dtoEntity;
        }
1 Answers

Generic type arguments need to be known at compile time ( i.e. when jitted). To construct object of a generic type at runtime you need use reflection. I.e. something as this answer ( Thanks to Marco for the link)

Type template = typeof(IEntityBaseRepository<>);
Type genericType = template.MakeGenericType(myType);
object instance = Activator.CreateInstance(genericType);

Ofc, to use the created object you need to use more reflection, since you do not have any compile-time type. You might end up with your compile time type, T, eventually so you can return it, but keep in mind that you will use a fair amount of reflection, and reflection tend to only produce runtime errors, and these errors might not be trivial to decipher.

As an alternative, if the goal is to map some set of types to some other types, one to one, you might consider using some other pattern, for example the visitor-pattern. This will require more code since you need some mapping code for each entity type. But it has the advantage of being type safe. Some types of implementations can force the developer to add any corresponding mappings when a new type is added, and therefore reduce the risk of runtime errors.

Since I do not know your particular circumstance I cannot know what the best solution is. But whenever dealing with reflection it can be a good idea to take a moment to consider if the benefit is worth the loss of type safety.

Related