【问题标题】:Cast PropertyInfo to generic type将 PropertyInfo 转换为泛型类型
【发布时间】:2011-06-10 09:50:16
【问题描述】:

我有以下课程:

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&lt;T&gt; 类型创建了扩展方法Clear()。使用反射,我能够检查AuthContext 的实例并将其所有DbSet&lt;T&gt; 类型的属性读取为PropertyInfo[]。如何将PropertyInfo 转换为DbSet&lt;T&gt; 以便在其上调用扩展方法?

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 时,您到底是什么意思?会发生什么
  • 您能否发布您的Clear() 方法的签名(不需要正文)以帮助@Daniel Hilgarth 和我澄清一些事情:)
  • 我想知道pi.PropertyType.IsGenericTypeDefinition 怎么不会因为你而失败。见this

标签: c# generics reflection casting


【解决方案1】:

请参阅 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&lt;T&gt; 更改为 dynamic 并更改了调用方法的方式。
因为Clear是扩展方法,所以不能直接在dynamic类型上调用,因为dynamic不知道扩展方法。但由于扩展方法并不比静态方法多多少,因此您始终可以将对扩展方法的调用更改为对静态方法的正常调用。
您所要做的就是将ExtensionClass 更改为定义Clear 的真实类名。

【讨论】:

  • @Daniel Hilgarth - 不错;但是如果Clear 是一种扩展方法,这会起作用吗?运行我得到Microsoft.CSharp.RuntimeBinder.RuntimeBinderException: 'Tests.A&lt;int&gt;' does not contain a definition for 'Clear' 的代码的快速还原。它当然适用于实例方法。您也不能将扩展显式调用为静态,因为它可能是 Clear&lt;T&gt; 并且需要一个通用参数。实际上,也许我们需要来自 OP 的方法声明
  • @Andras:啊……我错过了。已更正。
  • @Daniel Hilgarth - 是的;然后我编辑了我的评论(这永远行不通!);如果 OP 的方法是 Clear&lt;T&gt;,这将不起作用 - 我猜它必须适用于任何 DbSet&lt;T&gt; (在没有基类的情况下!)
  • @Daniel - 我不认为你真的这样做了 - 它在这里编译和运行;现在 that 很酷;我从没想过动态调度扩展到破解方法的正确泛型参数...+100
  • @Andras:是的,这行得通。运行时决定调用哪个方法。这很可能与将dynamic 传递给重载方法的机制相同,其中两个重载都只有一个参数,并且运行时仍然可以选择正确的重载。
【解决方案2】:

你的演员表错了。

您不能强制转换为 (DbSet&lt;T&gt;),因为这不是具体类型,除非 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&lt;T&gt;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 的回答,了解一种 cool 动态调度扩展方法调用而无需执行上述任何操作的方法(动态调度有效地完成了与上述类似的事情,但对你来说所有的缓存都在上面)。如果是我 - 我会使用它。

【讨论】:

  • 一种优化是使用dynamic。您可能需要添加一个示例。
  • @Daniel Hilgarth - 是的;有时我仍然认为 DIY 动态绑定 :)
  • @Daniel Hilgarth - 实际上,想为dynamic 的工作方式提供另一个解决方案吗?我已经考虑过dynamic 的情况,但不能轻易地看到从extnMethod&lt;T&gt;(this A&lt;T&gt;) 转换为dynamic 的好方法,它最终不需要在扩展方法中进行反射!我错过了一些我认为的东西
  • 看看我的回答。应该这样做。
  • 我也在考虑创建非通用版本public static void Clear(this DbSet set),但是.ForEach(pi = ((DbSet)pi.GetValue(currentContext, null)).Clear()); 行给了我无效的强制转换异常(无法将 DbSet 强制转换为 DbSet)。然而这条线正在工作:DbSet testCastEmp = (DbSet) currentContext.Employees。我错过了什么吗??
【解决方案3】:

您不能转换类型,因为它们之间没有任何关系。你会得到一个 PropertyInfo,它告诉你类型,但不是类型本身。

我想你会想要使用 Type.GetMethod 来定位“Clear”方法,作为 MethodInfo,然后你就可以调用 MethodInfo.Invoke。

【讨论】:

  • 他正在使用 GetValue 从 PropertyInfo 获取实例
  • 这不是问题,因为他没有尝试投射pi,而是pi.GetValue 的结果。 (@Andras:我知道你说了同样的话,但我想重新措辞一下,因为一开始我不明白你的评论...... ;-))
  • @Daniel Hilgarth - 完全没问题 :)
【解决方案4】:

您必须对 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&lt;&gt;类型和.Invoke它中检索它
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多