Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Activator.CreateInstance with a generic repository

I'm trying to play around with (what I think is) a factory that creates a repository depending on the enum being passed to the method. Looks like this:

RepositoryFactory

public class RepositoryFactory
{
    public IRepository<IEntity> GetRepository(FormTypes formType)
    {
        // Represents the IRepository that should be created, based on the form type passed
        var typeToCreate = formType.GetAttribute<EnumTypeAttribute>().Type;

        // return an instance of the form type repository
        IRepository<IEntity> type = Activator.CreateInstance(typeToCreate) as IRepository<IEntity>;

        if (type != null)
            return type;

        throw new ArgumentException(string.Format("No repository found for {0}", nameof(formType)));
    }
}

IRepository

public interface IRepository <T>
    where T : class, IEntity
{
    bool Create(IEnumerable<T> entities);

    IEnumerable<T> Read();

    bool Update(IEnumerable<T> entities);

    bool Delete(IEnumerable<T> entities);
}

FormTypes

public enum FormTypes
{
    [EnumType(typeof(Form64_9C2Repository))]
    Form64_9C2,

    [EnumType(typeof(Form64_9BaseRepository))]
    Form64_9Base
}

EnumExtensions

public static class EnumExtensions
{

    /// <summary>
    /// Get the Enum attribute
    /// </summary>
    /// <typeparam name="T">The attribute</typeparam>
    /// <param name="enumValue">The enum</param>
    /// <returns>The type to create</returns>
    public static T GetAttribute<T>(this System.Enum enumValue)
        where T : Attribute
    {
        FieldInfo field = enumValue.GetType().GetField(enumValue.ToString());
        object[] attribs = field.GetCustomAttributes(typeof(T), false);
        T result = default(T);

        if (attribs.Length > 0)
        {
            result = attribs[0] as T;
        }

        return result;
    }

}

Form64_9C2Repository

public class Form64_9C2Repository : IRepository<Form64_9C2>
{
    public bool Create(IEnumerable<Form64_9C2> entities)
    {
        throw new NotImplementedException();
    }

    public bool Delete(IEnumerable<Form64_9C2> entities)
    {
        throw new NotImplementedException();
    }

    public IEnumerable<Form64_9C2> Read()
    {
        throw new NotImplementedException();
    }

    public bool Update(IEnumerable<Form64_9C2> entities)
    {
        throw new NotImplementedException();
    }
}

IEntity

public interface IEntity { }

Form64_9C2 (stub)

public class Form64_9C2 : IEntity { }

Calling it all as:

class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine("Repository Factory Example \n\n");

        Business.Factory.RepositoryFactory factory = new Business.Factory.RepositoryFactory();

        // Get a 64 9C2 repository
        var repo9c2 = factory.GetRepository(FormTypes.Form64_9C2);
        Console.WriteLine(repo9c2);
    }
}

My problem is my type is always resolving to null. I'm expecting to get a NotImplementedException, but am instead getting the ArgumentException for not having a valid formType.

enter image description here

Prior to implementing IRepository<T> my type/repository was successfully being created (working code here), any ideas? I'm only just getting started playing around with factories, generics, and the like - so if I'm doing something way wrong please advise!

like image 883
Kritner Avatar asked Jan 04 '16 16:01

Kritner


1 Answers

Your code doesn't work for the exactly same reason for which this line doesn't compile:

IRepository<IEntity> repo = new Form64_9C2Repository();

Basically IRepository<IEntity> is not the same as IRepository<Form64_9C2> even if Form64_9C2 implements IEntity.

This could have worked if the T generic parameter on the IRepository interface was covariant:

public interface IRepository<out T> where T : class, IEntity
{
    IEnumerable<T> Read();    
}

But unfortunately this would mean that it can only appear as return type for the methods, not as parameter. Which is a no-go for your Update, Delete and Create methods. You could of course define a structure like that:

public interface IReadonlyRepository<out T> where T : class, IEntity
{
    IEnumerable<T> Read();    
}

public interface IRepository<T>: IReadonlyRepository<T> where T : class, IEntity
{
    bool Update(IEnumerable<T> entities);
    bool Delete(IEnumerable<T> entities);
    bool Create(IEnumerable<T> entities);
}

and have your GetRepository method return an IReadonlyRepository<IEntity>.

If this doesn't work for you you will need an additional parameter to specify the concrete entity type so that you perform the correct cast:

    public IRepository<TEntity> GetRepository<TEntity>(FormTypes formType) where TEntity: class, IEntity
    {
        // Represents the IRepository that should be created, based on the form type passed
        var typeToCreate = formType.GetAttribute<EnumTypeAttribute>().Type;

        // return an instance of the form type repository
        IRepository<TEntity> type = Activator.CreateInstance(typeToCreate) as IRepository<TEntity>;

        if (type != null)
            return type;

        throw new ArgumentException(string.Format("No repository found for {0}", nameof(formType)));
    }
}

and when calling in addition to specifying the repository type you will need to specify the entity type:

var repo9c2 = factory.GetRepository<Form64_9C2>(FormTypes.Form64_9C2);
like image 198
Darin Dimitrov Avatar answered Sep 26 '22 00:09

Darin Dimitrov