当外部库不向后兼容时检查 属性 是否存在

Checking if property exists when external library is not backward compatible

我试图在尝试访问之前检查第 3 方 dll 中是否存在 属性(为了向后兼容),为什么 if 语句 returns 为真然后当我访问 属性?

时失败并出现异常 Method not found: 'Boolean Shared.Models.Account.get_EnsureTextField()'
var type = am.account.GetType(); //account is of type class Account

//See if Account does not have property EnsureTextField
if(type.GetProperty("EnsureTextField") != null)
{
  cbEnsureTextField.Checked = am.account.EnsureTextField; //Exception thrown
} 

注意:代码编译正常,因为确实使用了 EnsureTextField 的编译时版本,问题是代码在 运行 时失败(当加载同一程序集的不同版本时) 即使我有 if 检查根据我的理解哪个应该防止异常。

从根本上说,尝试 运行 针对后来以向后不兼容的方式更改的库构建的代码是一个坏主意。虽然您可以如下所示解决它,但如果在各种地方看到怪癖,我不会感到惊讶,并且解决方法可能会变得越来越难以管理。如果完全有可能,最好退后一步并尝试更改您的依赖关系管理流程以完全避免这种情况。

话虽如此,我明白了失败的原因:JIT 编译器无法对您的方法进行 JIT 编译,因为它找不到在其中使用的方法引用 (get_EnsureText)方法。它甚至在评估 if 条件之前就失败了。

这里有一个小例子来证明这一点。从 Library.cs:

开始
public class Account
{
    public string Name { get; set; }
}

将其编译为 Library.dll

然后写Program.cs:

using System;

class Program
{
    static void Main(string[] args)
    {
        var account = new Account();
        if (DateTime.Now.Hour == 1000)
        {
            Console.WriteLine(account.Name);
        }
    }
}

请注意我们如何永远不会进入if语句的主体,因为它永远不会是 1000 点。

编译那个,参考Library.dll.

接下来,注释掉Library.cs中的Account.Name,重新编译Library.dll,然后重新运行 Program.exe:

Unhandled Exception: System.MissingMethodException: Method not found: 'System.String Account.get_Name()'.
   at Program.Main(String[] args)

如果 JIT 编译器不会实际执行它,我们可以通过阻止 JIT 编译器尝试访问 属性 来解决这个问题:

using System;

class Program
{
    static void Main(string[] args)
    {
        var account = new Account();
        if (DateTime.Now.Hour == 1000)
        {
            PrintAccountName(account);
        }
    }

    static void PrintAccountName(Account account)
    {
        Console.WriteLine(account.Name);
    }
}

跳完和之前一样的舞,这段代码现在执行无异常。

现在还没有使用反射...但是我们可以很容易地改变它来这样做。更改 Library.cs 以默认为帐户命名:

public class Account
{
    public string Name { get; set; } = "Default name";
}

然后将 Program.cs 更改为仅在 属性 存在时才使用它 - 但如果我们检查过它,甚至只调用直接引用 属性 的方法:

using System;

class Program
{
    static void Main(string[] args)
    {
        var account = new Account();
        if (account.GetType().GetProperty("Name") != null)
        {
            PrintAccountName(account);
        }
        else
        {
            Console.WriteLine("Account.Name is missing");
        }
    }

    static void PrintAccountName(Account account)
    {
        Console.WriteLine($"Account name: {account.Name}");
    }
}

经历与之前相同的舞蹈,但每次 运行 调用代码,我们最初得到输出:

Account name: Default name

但是删除属性并重新编译后,输出变为:

Account.Name is missing

...这就是你想要的。

有效,因为 JIT 编译器正在逐个方法地编译。我不知道有任何 保证 它会这样做,而不是推测性地编译一个类型中的所有方法,如果其中任何一个有问题就会失败。所以这是一个非常脆弱的解决方案,但它至少可能是您的临时解决方法。

替代方法

如问题评论中所述,您可以使用动态类型来避免将 属性 引用直接嵌入到 IL 中。下面是上面代码的一个略短的替代方案:

using System;

class Program
{
    static void Main(string[] args)
    {
        var account = new Account();
        if (account.GetType().GetProperty("Name") != null)
        {
            // Avoid a compile-time reference to the property
            dynamic d = account;
            Console.WriteLine($"Account name: {d.Name}");
        }
        else
        {
            Console.WriteLine("Account.Name is missing");
        }
    }
}

就不依赖 JIT 编译器实现细节而言,这肯定更简短,而且可能更可靠。另一方面,它更容易出现拼写错误等,因为 d.Name 表达式在编译时根本 没有被检查。 (实际上,Program.cs 代码即使针对新版本的库仍然可以编译。)

旁注

这段代码只测试一个属性。它可能最初是 read/write 属性 但后来变成只写的,在这种情况下我们仍然会尝试调用 get 访问器,但失败了。不过,可以修改您正在检查的条件以合理轻松地处理此问题。