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

时间:2018-04-21 20:15:26

标签: c# backwards-compatibility system.reflection

我试图在尝试访问之前检查第三方dll中是否存在属性(为了向后兼容),为什么if语句返回true然后失败,异常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,问题是代码在运行时失败(当加载相同程序集的不同版本时),即使我有{ {1}}根据我的理解检查哪一项可以防止异常。

1 个答案:

答案 0 :(得分:2)

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

说了这些,我可以看出它失败的原因:JIT编译器无法JIT编译你的方法,因为它无法找到方法引用({{1} })在方法中使用。它甚至在评估get_EnsureText条件之前就失败了。

这里有一个小例子来证明这一点。从if开始:

Library.cs

将其编译为public class Account { public string Name { get; set; } }

然后编写Program.cs:

Library.dll

请注意我们永远不会进入using System; class Program { static void Main(string[] args) { var account = new Account(); if (DateTime.Now.Hour == 1000) { Console.WriteLine(account.Name); } } } 语句的正文,因为它永远不会是1000点钟。

参考if

编译

接下来,在Library.dll中注释掉Account.Name并重新编译Library.cs,然后重新运行Library.dll

Program.exe

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

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

和以前一样经历同样的舞蹈,此代码现在无异常地执行。

现在还没有使用反射...但我们可以轻松改变它。更改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

......这就是你想要的。

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

替代方法

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

Account.Name is missing

这肯定更简洁,并且在不依赖JIT编译器实现细节方面可能更强大。另一方面,它更容易出现拼写错误等,因为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"); } } } 表达式在编译时未被检查 。 (实际上,d.Name代码仍然会针对新版本的库进行编译。)

<强>侧面注意

此代码仅测试属性。它可能是最初的读/写属性,但后来变为只写,在这种情况下,我们仍然会尝试调用get访问器,并且失败。您可以修改您要检查的条件,以便合理地处理这个问题。