将PropertyInfo转换为泛型类型

4

我有以下的类:

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; } 
    // ...
}

我为类型DbSet<T>创建了扩展方法Clear()。使用反射技术,我能够检查AuthContext实例,并将所有类型为DbSet<T>的属性读取为PropertyInfo[]。如何将PropertyInfo强制转换为DbSet<T>,以便在其上调用扩展方法?

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

当你说 !!!THIS WILL NOT WORK 时,你到底是什么意思?会发生什么? - Andras Zoltan
你能发布一下你的 Clear() 方法的签名(不需要方法体),以帮助 @Daniel Hilgarth 和我澄清一些问题吗? :) - Andras Zoltan
我想知道 pi.PropertyType.IsGenericTypeDefinition 在你那里为什么不会失败。请参见此链接 - Veverke
4个回答

4
请查看Andras Zoltan的答案,了解你正在做错的事情。
然而,如果你使用.NET 4.0,你不需要使用反射调用方法,你可以简单地使用新的"dynamic"关键字:
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)));

我将DbSet<T>的类型更改为dynamic并更改了方法的调用方式。
因为Clear是一个扩展方法,不能直接在dynamic类型上调用,因为dynamic不知道扩展方法。但是,由于扩展方法不过是静态方法的变形,您可以随时将对扩展方法的调用更改为对静态方法的正常调用。
您要做的一切就是将ExtensionClass更改为定义Clear的实际类名。


@Daniel Hilgarth - 很好;但是如果Clear是一个扩展方法,这个方法还能工作吗?运行代码的快速重现我得到了Microsoft.CSharp.RuntimeBinder.RuntimeBinderException: 'Tests.A<int>' does not contain a definition for 'Clear'。它肯定适用于实例方法。你也不能显式地将扩展作为静态调用,因为它可能是Clear<T>并需要一个泛型参数。实际上,也许我们需要来自OP的方法声明。 - Andras Zoltan
@Andras:啊...我错过了那个。已经更正了。 - Daniel Hilgarth
@Daniel Hilgarth - 是的;然后我编辑了我的评论(这从来不起作用,是吧!);如果OP的方法是Clear<T>,那么这个方法将无法工作——我猜它必须这样才能在任何DbSet<T>上工作(在没有基类的情况下!) - Andras Zoltan
@Daniel - 我不认为你真的做到了 - 它正在这里编译和运行;现在很酷;我从没想过动态调度会扩展到破解方法的正确泛型参数... +100 - Andras Zoltan
@Daniel:是的,这是一种需要平衡的情况,你必须权衡引导OP解决眼前问题和批评他们方法可能会破坏他们世界的需求。 - Andras Zoltan
显示剩余4条评论

1

你的类型转换是错误的。

你不能将它转换为(DbSet<T>),因为这不是一个具体的类型,除非T被定义在泛型方法或泛型类型内部。

你有几种可能性。

如果DbSet有一个基类(例如,在我下面的代码中DbSet_BaseClass)仍可以实现你的Clear()方法 - 那么改变它的签名从:

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

到:

public static void Clear(this DbSet_BaseClass)

接下来,您可以在 .ForEach 中更改您的转换为 ((DbSet_BaseClass)pi.GetValue...

如果您不能这样做,您可以通过为 DbSet<T> 构建特定的通用版本来反射调用 Clear 扩展方法:

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

然后,给定一个属性信息和上下文实例:

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) });

你可以在这个基础上进行很多优化,比如缓存委托等等,但这个方法是可行的。

更新

或者查看@Daniel Hilgarth的答案,他提供了一种酷炫的方法来动态调用扩展方法,而不需要做任何上述操作(动态调度实际上会为您执行类似于上述操作的所有缓存)。如果是我,我会使用那个方法。


1
一种优化方法是使用 dynamic。您可能想添加一个示例。 - Daniel Hilgarth
@Daniel Hilgarth - 啊,是的;有时我仍然会想到 DIY 动态绑定 :) - Andras Zoltan
@Daniel Hilgarth - 实际上,你能否提供另一种解决方案来说明你如何看待dynamic的工作原理?我对dynamic的情况有了一些想法,但是很难找到一种不需要在扩展方法中使用反射就可以将extnMethod<T>(this A<T>)转换为dynamic的好方法!我觉得我错过了什么。 - Andras Zoltan
看一下我的回答。这应该可以解决问题。 - Daniel Hilgarth
我也在考虑创建非泛型版本的 public static void Clear(this DbSet set),但是这一行 .ForEach(pi = ((DbSet)pi.GetValue(currentContext, null)).Clear()); 给了我一个无效转换异常(无法将 DbSet<> 转换为 DbSet)。然而,这一行代码是有效的:DbSet testCastEmp = (DbSet) currentContext.Employees。我有什么遗漏吗? - michal.kohut

0

你不能将这些类型进行转换,因为它们之间没有关联。你得到的是一个PropertyInfo对象,它告诉你有关类型的信息,但并不是类型本身。

我认为你需要使用Type.GetMethod来定位"Clear"方法,并得到一个MethodInfo对象,然后你就可以调用MethodInfo.Invoke了。


2
他正在使用 GetValue 从 PropertyInfo 获取实例。 - Andras Zoltan
1
这不是问题,因为他并没有试图转换 pi 而是 pi.GetValue 的结果。 (@Andras: 我知道你说了同样的话,但我想重新表达一下,因为我一开始没有理解你的评论... ;-)) - Daniel Hilgarth
@Daniel Hilgarth - 没问题 :) - Andras Zoltan

0

你需要对DbSet进行反射以调用Clear方法。

试试这个:

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);
                      }
                      );

这是一个扩展方法,因此无法从“DbSet<>”类型中检索它并调用.Invoke - Andras Zoltan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接