【问题标题】:Why do my assemblies need to be loaded in a specific order?为什么我的程序集需要按特定顺序加载?
【发布时间】:2018-01-17 04:35:23
【问题描述】:

我正在编写一个简单的插件,偶然发现 contractType.IsAssignableFrom(pluginType) 根据加载顺序返回不同的结果。

在插件上调用 IsAssignableFrom 按预期返回 True。
但是如果我在加载插件之前加载合同程序集,插件上的 IsAssignableFrom返回 False。

我正在运行 Win10 和 dotnet4.7,但我怀疑这有什么相关性。


代码

[TestMethod]
public void SimplyLoadingPlugin_Succeeds()
{
    var plugin = Assembly.LoadFrom(PluginPathFilename);
    var res = typeof(Contract).IsAssignableFrom(plugin.GetExportedTypes().Single());

    Assert.IsTrue(res); // Succeeds.
}

[TestMethod]
public void LoadingContractAndThenPlugin_Fails()
{
    var contract = Assembly.LoadFrom(ContractPathFilename);
    var plugin = Assembly.LoadFrom(PluginPathFilename);
    var res = typeof(Contract).IsAssignableFrom(plugin.GetExportedTypes().Single());

    Assert.IsTrue(res); // Fails.
}

为了让测试更难:
如果我自己运行LoadingContractAndThenPlugin_Fails 测试失败。但是如果我一起运行测试,它取决于顺序。首先运行 SimplyLoadingPlugin_Succeeds 和最后运行 LoadingContractAndThenPlugin_Fails 会使两个测试都变为绿色,但以相反的顺序运行它们会使两者都失败。
所以不知何故,在插件之前加载合同给我带来了一些麻烦。
我在 GAC 中看不到任何相关内容。


以下是所需的所有文件。中的路径可能需要更新。
4 个项目,每个项目一个文件。 1 个解决方案。

Contract.cs(一个库项目)

public abstract class Contract
{
    public abstract int Version { get; set; }
}

Plugin.cs(一个库项目)

public class Plugin : Contract
{
    public override int Version { get; set; }
}

Tests.cs(一个测试项目)

[TestClass]
public class Tests
{
    private const string PluginPath = @"C:\DATA\Projekt\LoadFromOrder\Plugin\bin\Debug";
    private string PluginPathFilename = Path.Combine(PluginPath, "Plugin.dll");
    private string ContractPathFilename = Path.Combine(PluginPath, "Contract.dll");

    [TestMethod]
    public void SimplyLoadingPlugin_Succeeds()
    {
        var plugin = Assembly.LoadFrom(PluginPathFilename);
        var res = typeof(Contract).IsAssignableFrom(plugin.GetExportedTypes().Single());

        Assert.IsTrue(res); // Succeeds.
    }

    [TestMethod]
    public void LoadingContractAndThenPlugin_Fails()
    {
        var contract = Assembly.LoadFrom(ContractPathFilename);
        var plugin = Assembly.LoadFrom(PluginPathFilename);
        var res = typeof(Contract).IsAssignableFrom(plugin.GetExportedTypes().Single());

        Assert.IsTrue(res); // Fails.
    }

    // BEGIN ---- Update. ----
    [TestMethod]
    public void LoadingPluginFromTestProject_Succeeds()
    {
        var contract = Assembly.LoadFrom(
            @"C:\DATA\Projekt\LoadFromOrder\TestProject\bin\Debug\Contract.dll");
        var plugin = Assembly.LoadFrom(PluginPathFilename);
        var res = typeof(Contract.Contract).IsAssignableFrom(plugin.GetExportedTypes().Single());

        Assert.IsTrue(res); // Succeeds.
    }
    // END ---- Update. ----

}

Program.cs(控制台项目)

class Program
{
    static void Main(string[] args)
    {
        var tests = new Tests();
        try
        {
            System.Console.WriteLine("Press A for Success and B for Fail.");
            switch (System.Console.ReadKey(true).Key)
            {
                case ConsoleKey.A:
                    tests.SimplyLoadingPlugin_Succeeds();
                    break;
                case ConsoleKey.B:
                    tests.LoadingContractAndThenPlugin_Fails();
                    break;
            }
            System.Console.WriteLine("SUCCESS");
        }
        catch (Exception exc)
        {
            System.Console.WriteLine($"FAIL: {exc.Message}");
        }
    }
}

【问题讨论】:

  • 由于您的两个测试都直接引用 Contract 类型,因此必须加载包含该类型的程序集,以便这些方法能够 JIT 编译。因此,我认为您并没有像想象的那样控制订购。
  • 同意。 Assembly.LoadFrom vs Assembly.LoadFile 控制 不在我的手中。但不知何故,在加载插件之前加载合同,然后加载合同,这会有所不同。就像我已经将 Contract´ 加载到我的程序集中,然后它阻止 Plugin 找到 Contract´´。我只是回答了我自己的问题吗?但仍然没有补救措施。
  • 我尝试使用 1 个控制台应用程序和 2 个类库项目在一个新解决方案中重新创建您的问题,但我无法获得您所描述的行为......你能不能尝试为您的问题创建Minimal, Complete, and Verifiable example,以便我们重新创建问题?
  • 您确定您的 testproject 不包含任何可能导致对引用的 dll 之一进行重建的项目引用吗?
  • @bassfader 非常感谢您花时间解决我的问题。我已经为测试添加了类,因此路径常量的复制/粘贴和更新应该足以重现问题。我确实在重新启动的系统上创建了一个全新的解决方案并重现了该问题;运行他们成功的所有测试。运行..._Fails 测试失败。正是这个文本被复制到我的问题中。

标签: c# .net


【解决方案1】:

通过使用Assembly.LoadFrom 加载Contract,您正在创建引用歧义。该库已经加载,这就是为什么我们可以这样做 typeof(Contract) 无需再次加载它......

我的建议:使用反射来确定你有哪些引用,只加载那些不存在的引用,这里是一个示例代码 sn-p:

        var dllFiles = Directory.GetFiles(DIR, "*.DLL", SearchOption.AllDirectories);
        var plugins = new HashSet<Assembly>();

        var references = typeof(Program).Assembly.GetReferencedAssemblies();
        foreach (var dllPath in dllFiles)
        {
            string name = Path.GetFileNameWithoutExtension(dllPath);
            if (!references.Any(x => x.Name == name) && !plugins.Any(x => x.GetName().Name == name))
                plugins.Add(Assembly.LoadFrom(dllPath));
        }

在该示例中,我们获取给定目录(包括子目录)的每个 DLL,DIR 可以是像 ..\..\.. 这样的相对路径,并且只加载那些不在程序集引用中的路径。


这是包含两个插件项目的完整解决方案:
https://github.com/heldersepu/csharp-proj/tree/master/PluginSystem

【讨论】:

  • 如果你想加快搜索速度 (Directory.GetFiles),你应该让你所有的插件都遵循一个共同的命名方式,而不是 *.DLL,你可以有类似 Plugin*.DLL 的东西。
  • Contract 已被测试程序加载,否则无法编译。然后可以再次加载它。但不是第三次......
  • 是的:Contract 已经加载,我认为我的回答涵盖了这一点......而且两次加载任何内容都不行,它效率低下,并且可能导致引用歧义。跨度>
  • 有趣,我猜测试项目中的 Contract 程序集可能是从 Projekt\LoadFromOrder\SomeTestProject 加载的,当您从另一个路径(即 Projekt\LoadFromOrder\Plugin)加载相同的 dll 时,您会产生歧义。如果您的 Contract dll 总是same 路径(即Projekt\LoadFromOrder\Plugin)引用并且在构建时没有local copying,则问题可能会消失。
  • @John-Philip Dotnet 对程序集的加载位置做出反应,听起来似乎很合理。不幸的是,我现在无法测试它,因为我遇到了硬件崩溃,因此手头突然有更紧急的事情。
【解决方案2】:

正如@HelderSepu 所说,第二次加载合同程序集可能是问题所在。

我建议您以不同但更简单的方式进行测试。而不是手动(重新)加载测试中的程序集,只需在测试项目中添加对程序集/项目的引用,并直接引用typeof(Contract)typeof(Plugin)并检查是否typeof(Contract).IsAssignableFrom(typeof(Plugin))。没什么复杂的,只需添加对测试项目的引用即可。

您不需要测试程序集是否正确加载,CLR 会处理它。您需要测试插件程序集是否包含Contract 定义。无论您的架构可能有什么用例,您应该担心的不是程序集加载是否有效;是插件是否被正确实现。

【讨论】:

  • 建议的解决方案不能在我选择的架构中使用,因为引用插件会破坏插件系统的目的。照这样说;有人可能会学到一些东西,所以我尝试从测试项目中同时引用ContractPlugin,但它并没有真正改变任何东西。我测试项目没有加载Plugin,只是因为它被引用了;当它加载它时,它来自于它被要求加载的路径。
猜你喜欢
  • 1970-01-01
  • 2016-08-23
  • 2013-08-30
  • 2011-08-18
  • 1970-01-01
  • 2012-02-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多