使用强命名程序集中的反射创建类型实例的问题

时间:2011-06-02 13:35:04

标签: .net reflection

我有一个部署在客户端上的现有.NET 2.0 Win Forms解决方案。为了这个场景,我们称之为'WinApp.exe'。 WinApp.exe直接引用名为“Assembly.dll”的专用程序集。 Assembly.dll直接引用另一个名为“Framework.dll”的程序集。 Framework.dll中是一个名为“IPlugIn”的类型。 Framework.dll没有强名称,但程序集的版本是1.0。 Assembly.dll中有一个类从Framework.dll实现IPlugIn。

我有另一个名为'Framework.exe'的.NET 2.0 Win Forms解决方案。此Win Forms项目还直接引用Framework.dll,其中定义了“IPlugIn”类型。 Framework.dll没有强名称,但此程序集的版本是2.0。一点一点,Framework.dll v2中的'IPlugIn'与Framework.dll v1中的'IPlugIn'相同。 Framework.exe中的代码使用反射来加载WinApp中的IPlugIn实现:

AssemblyName an = AssemblyName.GetAssemblyName("C:\\Program Files\\WinApp\\Assembly.dll");
Assembly dll = Assembly.Load(an);
object o = Activator.CreateInstance(dll.GetType("WinApp.ClassThatImplementsIPlugIn"));
IPlugIn iPlugIn = o as IPlugIn;

到目前为止,这么好。这段代码有效!现在这是麻烦的部分。我需要为Framework.dll v2分配一个强名称,以便它可以放在GAC中。但是,WinApp及其依赖程序集将不会重新部署 - 它必须继续使用它当前使用的相同版本的程序集。当我给Framework.dll v2一个强名称并重新编译并运行Framework.exe时,当上面的代码执行时,我会在线上收到“InvalidCastException”:

IPlugIn iPlugIn = o as IPlugIn;

我想我收到了这个异常,因为现在一个版本的Framework.dll有一个强名称,运行时将类型“IPlugIn”视为完全不同的类型。我需要知道是否有任何方法可以解决这个问题。同样,要求是:

  1. 我必须能够将Framework.dll v2放在GAC中(因此它必须具有强名称)。
  2. WinApp必须继续像现在一样工作(继续引用Framework.dll v1)。我无法为WinApp重新分发新编译的程序集。
  3. 提前致谢!

    乍得

1 个答案:

答案 0 :(得分:4)

这里的问题是加载了作为旧Framework程序集的强名称Framework。 .NET并不关心IPlugin的两个定义是否相同。它们来自一个不同的程序集,所以它们是不同的(我有点困惑,为什么它会引发InvalidCastException作为一个演员,如果失败就会返回null

选项A

仍然使用该类的一种方法是使用反射,但是当Framework中托管的更多类型发挥作用时,这可能会有很长的路要走。所有这些都需要使用反射来访问。 您可以使用以下内容创建包装器来隐藏反射:

public class PluginWrapper : IPlugin
{
  object fObj;
  PropertyInfo fNameProperty;
  MethodInfo fGetOtherMethod;

  public PluginWrapper(object o)
  {
    fObj = o;
    fNameProperty = o.GetType().GetInterface("IPlugin").GetProperty("Name");
    fGetOtherMethod = o.GetType().GetInterface("IPlugin").GetMethod("GetOther", new Type[] { typeof(string) });
  }

  public string Name
  {
    get { return (string)fNameProperty.GetValue(fObj, null); }
  }

  public IOther GetOther(string name)
  {
    object result = fGetOtherMethod.Invoke(fObj, new object[] { name });

    if (result == null)
      return null;

    return new OtherWrapper(result);
  }
}

然后您可以使用以下对象:

IPlugin iPlugIn = new PluginWrapper(o);

选项B

我可以想到另一种方式。我不确定你是否应该走这条路,因为它在我眼中看起来很像“黑客”,但无论如何我会分享它并让你决定。

您可以使用字节流而不是AssemblyName来加载程序集。这样,它就无法解析其他Framework程序集并让您更改以突破AssemblyResolve事件:

AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(CurrentDomain_AssemblyResolve);
Assembly dll = Assembly.Load(File.ReadAllBytes(@"C:\Program Files\WinApp\Assembly.dll"));
object o = Activator.CreateInstance(dll.GetType("WinApp.ClassThatImplementsIPlugIn"));
IPlugin iPlugIn = o as IPlugin;

static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
{
  if (args.Name.ToLower().StartsWith("framework,"))
    return typeof(IPlugin ).Assembly;

  return null;
}

这可能有点棘手,因为您将向.NET承诺程序集兼容并且您已准备好承担风险。如果事情不相容,那么很快就会出现问题。

其他选项

可能还有一些方法可以使用bindingredirect中的app.config元素来解决问题。关于它是如何工作的细节,我并不知道,但可能值得深入研究。