版本之间的界面变化 - 如何管理?

Sha*_*ica 6 c# versioning plugins

这是我们在客户网站上遇到的一种相当不愉快的泡菜.客户端有大约100个工作站,我们在其上部署了产品"MyApp"的1.0.0版本.

现在,该产品所做的一件事是加载一个加载项(称之为"MyPlugIn",它首先在中央服务器上查找是否有更新的版本,如果是,则复制该文件在本地,然后它使用Assembly.Load并调用某个已知的接口加载加载项.这已经好几个月了.

然后客户想在某些机器上安装我们产品的v1.0.1(但不是全部).随之而来的是MyPlugIn的新版本.

但随后出现了问题.有一个共享DLL,由MyApp和MyPlugIn引用,称为MyDLL,它有一个方法MyClass.MyMethod.在v1.0.0和v1.0.1之间,MyClass.MyMethod更改了签名(添加了一个参数).现在新版本的MyPlugIn导致v1.0.0客户端应用程序崩溃:

找不到方法:MyClass.MyMethod(System.String)

客户端明确地不希望在所有客户端站点上部署v1.0.1,因为v1.0.1中包含的修复程序仅对少数工作站是必需的,并且不需要将其推广到所有客户端.遗憾的是,我们还没有(还)使用ClickOnce或其他大规模部署实用程序,因此推出v1.0.1将是一项痛苦而且不必要的练习.

有没有办法在MyPlugin中编写代码,以便它能够同样正常工作,无论它是处理MyDLL v1.0.0还是v1.0.1?也许在实际调用它之前,有一些方法可以使用反射来探测预期的接口,看它是否存在?

编辑:我还应该提一下 - 我们有一些非常严格的QA程序.由于QA已正式发布v1.0.1,因此我们不允许对MyApp或MyDLL进行任何更改.我们唯一的行动自由是更改MyPlugin,这是专门为该客户编写的自定义代码.

Ste*_*eve 3

我从我前段时间编写的应用程序中提取了这段代码并删除了一些部分。
这里假设了很多事情:

  1. MyDll.dll 的位置是当前目录
  2. 获取反射信息的命名空间是“MyDll.MyClass”
  3. 该类有一个不带参数的构造函数。
  4. 您不期望返回值
using System.Reflection;

private void CallPluginMethod(string param)
{
     // Is MyDLL.Dll in current directory ??? 
     // Probably it's better to call Assembly.GetExecutingAssembly().Location but....
     string libToCheck = Path.Combine(Environment.CurrentDirectory, "MyDLL.dll");  
     Assembly a = Assembly.LoadFile(libToCheck);
     string typeAssembly = "MyDll.MyClass"; // Is this namespace correct ???
     Type c = a.GetType(typeAssembly);

     // Get all method infos for public non static methods 
     MethodInfo[] miList = c.GetMethods(BindingFlags.Public|BindingFlags.Instance|BindingFlags.DeclaredOnly);
     // Search the one required  (could be optimized with Linq?)
     foreach(MethodInfo mi in miList)
     {
         if(mi.Name == "MyMethod")
         {
             // Create a MyClass object supposing it has an empty constructor
             ConstructorInfo clsConstructor = c.GetConstructor(Type.EmptyTypes);
             object myClass = clsConstructor.Invoke(new object[]{});

             // check how many parameters are required
             if(mi.GetParameters().Length == 1)
                 // call the new interface 
                 mi.Invoke(myClass, new object[]{param});
             else 
                 // call the old interface or give out an exception
                 mi.Invoke(myClass, null);
             break;
         }
     }
}
Run Code Online (Sandbox Code Playgroud)

我们在这里做什么:

  1. 动态加载库并提取MyClass.
  2. 使用该类型,向反射子系统询问该类型中存在的列表MethodInfo
  3. 检查每个方法名称以找到所需的方法。
  4. 当找到该方法时,构建该类型的实例。
  5. 获取该方法期望的参数数量。
  6. 根据参数的数量,使用 调用正确的版本Invoke