0

Goal

I would like to be able to count the number of times a specific PrimaryImageId is used within a number of different db entities.

Their classes each have a PrimaryImageId property and are adorned with a [HasPrimaryImage] attribute. They also implement an interface IHasPrimaryImage.

I may not know these classes in advance, so I want to use the [HasPrimaryImage] attribute to identify them, not hard code in the types.

I've Tried

I am using a generic repository method. But although it works when called with 'hard-coded' types:

GetCount<NewsItem>(x => x.PrimaryImageId == id);

...I can't get it to work when the type argument is provided from reflection.

var types = GetTypesWithHasPrimaryImageAttribute();
foreach(Type t in types)
{
    GetCount<t>(x => x.PrimaryImageId == id);
}

I've tried calling GetCount<t>(), GetCount<typeof(t)> and a few other silly things.

It appears that I can't call a generic method using a reflection generated type.

Question

Jon Skeet recommends using MakeGenericMethod in his answer, but I'm struggling to do that and wondering if that is overkill for my requirements. Is there an easier / better way to achieve what I'm after?



Db Entity Classes

[HasPrimaryImage]
public class NewsItem 
{
    public int PrimaryImageId { get; set; }

    // .. other properties
}


[HasPrimaryImage]
public class Product 
{
    public int PrimaryImageId { get; set; }

    // .. other properties
}

Generic Repository Method

public virtual int GetCount<TDataModel>(Expression<Func<TDataModel, bool>> wherePredicate = null)
    where TDataModel : class, IDataModel
{
    return GetQueryable<TDataModel>(wherePredicate).Count();
}

Get All Classes with HasPrimaryImage Attribute:

public static IEnumerable<Type> GetTypesWithHasPrimaryImageAttribute()
{
    var currentAssembly = Assembly.GetExecutingAssembly();
    foreach (Type type in currentAssembly.GetTypes())
    {
        if (type.GetCustomAttributes(typeof(HasPrimaryImageAttribute), true).Length > 0)
        {
            yield return type;
        }
    }
}   
Martin Hansen Lennox
  • 2,837
  • 2
  • 23
  • 64
  • 2
    in general skeet provides the correct answer. In particular you want to specify the type for a generic method at run time. MakeGenericMethod is how to do that – pm100 Feb 27 '18 at 23:27

1 Answers1

1

If you have obtained the MethodInfo, then you can use its MakeGenericMethod method to specify its generic type argument before making the call via reflection.

MethodInfo getCountMethod = my Objectives. GetType() .GetMethod(
    "GetCount", 
    BindingFlags.Public | BindingFlags.Instance | ...); // Fill the right flags

var types = GetTypesWithHasPrimaryImageAttribute();
Func<Entity, bool> predicate = x => x.PrimaryImageId == id;

foreach(Type t in types)
{
     getCountMethod.MakeGenericMethod(type).Invoke(myObj, predicate);
}

I haven't been able to run this code, so please forgive me if there is some omission there. But this should be the working principle and you will be able to make it work for you.

Zoran Horvat
  • 10,924
  • 3
  • 31
  • 43
  • Thank you, that's very helpful. Is `myObj` a normally instantiated instance of the generic repository? – Martin Hansen Lennox Feb 27 '18 at 23:32
  • I suppose it is - that is the object on which the `GetCount` method is defined. Now you pull that out into a `MethodInfo`, fill the generic type in and then invoke it by passing the same `myObj` as the instance on which the method should execute. – Zoran Horvat Feb 27 '18 at 23:33
  • 1
    Your example uses `myObj` both as a type (the call to `GetMethod`) and an instance of that type (the call to `Invoke`). The first of those should really be `myType` (assuming you have a `Type` with that name) – pinkfloydx33 Feb 28 '18 at 00:39
  • @pinkfloydx33 - so glad you said that, I really struggled to make it work, until I passed an *instance* of the repository in instead of the type. Your comment affirms I've done it correctly. No disrespect to Zoran who did 99% of the work for me in order to get it working! Really big thanks to both of you. – Martin Hansen Lennox Feb 28 '18 at 00:59
  • @pinkfloydx33 Sorry, I missed the call to GetType(). Now it's fixed but you've figured it already. – Zoran Horvat Feb 28 '18 at 08:31