Cast PropertyInfo to generic type

2019-04-10 17:53发布

问题:

I have the following class:

public class AuthContext : DbContext
{
    public DbSet<Models.Permission> Permissions { get; set; }
    public DbSet<Models.Application> Applications { get; set; }
    public DbSet<Models.Employee> Employees { get; set; } 
    // ...
}

I created the extension method Clear() for type DbSet<T>. Using reflection I am able to inspect the instance of AuthContext and read all its properties of type DbSet<T> as PropertyInfo[]. How can I cast the PropertyInfo to DbSet<T> in order to call the extension method on it ?

var currentContext = new AuthContext();
...
var dbSets = typeof(AuthContext).GetProperties(BindingFlags.Public | BindingFlags.Instance);
dbSets.Where(pi =>
                pi.PropertyType.IsGenericTypeDefinition &&
                pi.PropertyType.GetGenericTypeDefinition() == typeof(DbSet<>)).ToList()
      .ForEach(pi = ((DbSet<T>)pi.GetValue(currentContext, null)).Clear()); // !!!THIS WILL NOT WORK

回答1:

Please see Andras Zoltan's answer for an explanation of what you are doing wrong.

However, if you use .NET 4.0, you don't need to use reflection to call the method, you can simply use the new dynamic keyword:

var currentContext = new AuthContext();
var dbSets = typeof(AuthContext).GetProperties(BindingFlags.Public | 
                                               BindingFlags.Instance);
dbSets.Where(pi => pi.PropertyType.IsGenericType &&
                   pi.PropertyType.GetGenericTypeDefinition() == typeof(DbSet<>))
      .ToList()
      .ForEach(pi => ExtensionClass.Clear((dynamic)pi.GetValue(currentContext, 
                                                               null)));

I changed the cast from DbSet<T> to dynamic and changed the way the method is called.
Because Clear is an extension method, it can't be called directly on the dynamic type, because dynamic doesn't know about extension methods. But as extension methods are not much more than static methods, you can always change a call to an extension method to a normal call to the static method.
Everything you have to do is to change ExtensionClass to the real class name in which Clear is defined.



回答2:

Your cast is wrong.

You can't cast to (DbSet<T>) because that's not a concrete type unless T is defined inside a generic method or generic type.

You have a couple of possibilities.

If DbSet has a base class (e.g. DbSet_BaseClass in my code below) from which you can still implement your Clear() method - then change it's signature from:

public static void Clear<T>(this DbSet<T>)

to:

public static void Clear(this DbSet_BaseClass)

Then you can change your cast in the .ForEach to ((DbSet_BaseClass)pi.GetValue...

If you can't do that, you could reflect-invoke the Clear extension method by building a specific generic version of it for the T of the DbSet<T>:

MethodInfo myClearMethod = typeof(container_type).GetMethod(
  "Clear", BindingFlags.Public | BindingFlags.Static);

Then, given a property info and context instance:

Type propType = pi.PropertyType;
Type typeofT = propType.GetGenericArguments[0];
MethodInfo toInvoke = myClearMethod.MakeGenericMethod(typeofT);
//now invoke it
toInvoke.Invoke(null, new[] { pi.GetValue(currentContext, null) });

There are lots of optimisations you can put on top of this, caching delegates etc etc, but this will work.

Update

Or see @Daniel Hilgarth's answer for a cool way to dynamically dispatch the call to the extension method without having to do any of the above (dynamic dispatch effectively does something like the above, but for you with all the caching on top). If it were me - I'd be using that.



回答3:

You can't cast the types because they've got no relationship to each other. You're getting a PropertyInfo which tells you about the type, but is not the type itself.

I think you're going to want to use Type.GetMethod to locate the "Clear" method, as a MethodInfo, and then you'll be able to call MethodInfo.Invoke.



回答4:

You have to do reflection on the DbSet to invoke the Clear Method

Try this :

var dbSets = typeof(AuthContext).GetProperties(BindingFlags.Public | BindingFlags.Instance);
            dbSets.Where(pi =>
                            pi.PropertyType.IsGenericType &&
                            pi.PropertyType.GetGenericTypeDefinition() == typeof(DbSet<>)).ToList()
                  .ForEach(pi =>
                      {
                          typeof(DbSet<>)
                              .MakeGenericType(pi.PropertyType.GetGenericArguments()[0])
                              .GetMethod("Clear")
                              .Invoke(pi.GetValue(currentContext, null), null);
                      }
                      );