2

I am trying to check if a property exists in a 3rd party dll (for backward compatibility) before trying to access, why does the if statement returns true and then fail with exception Method not found: 'Boolean Shared.Models.Account.get_EnsureTextField()' when I access the property?

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
} 

Note: code compiles fine as at compile time version that indeed has EnsureTextField is used, the issue is that code fails at run-time (when different version of the same assembly is loaded) even if I have if check which based on my understanding should prevent the exception.

Alexei Levenkov
  • 98,904
  • 14
  • 127
  • 179
xoail
  • 2,978
  • 5
  • 36
  • 70
  • It is not dynamic. `am` is an instance of a class `AccountManager` which has a property `account` of type `Account` which in-tern may or may not (depending on version of dll) have a property of type `bool`. – xoail Apr 21 '18 at 20:31
  • Just to clarify: could you please confirm that exception is actually thrown before even getting to `if` at the time you try to call your method (as method can't be JITed as @dasblinkenlight said). – Alexei Levenkov Apr 21 '18 at 20:33
  • 1
    I don't get it, if the property may not be there, how does the compiler let you compile the code accessing it? – Sergey Kalinichenko Apr 21 '18 at 20:35
  • Since this is an external dll, when I write it, it does include the property. But I am trying to mimic a scenario by including it with older version of the external dll that does not have this property. Basically I would like my app to gracefully ignore if dependent property does not exists. Hence on the external dll `account.EnsureTextField` may or may not exits depending on version of dll that is included. – xoail Apr 21 '18 at 20:38
  • @dasblinkenlight common case: you compile against v2 version of DLL (which has the property), at run time v1 or v3 is loaded (which no longer has the property). When method using this property get JITed JIT will throw missing method exception since it can't compile IL into executable code *at run-time*. Your original "is account `dynamic`" comment would be an answer how to fix it if OP actually confirms that this is the case... – Alexei Levenkov Apr 21 '18 at 20:47
  • 1
    @AlexeiLevenkov: I've added the `dynamic` approach to my answer. – Jon Skeet Apr 21 '18 at 21:16
  • Almost duplicate: [Why does short-circuiting not prevent MissingMethodException related to unreachable branch?](https://stackoverflow.com/q/5233155/103167) – Ben Voigt Apr 21 '18 at 21:18
  • @BenVoigt I agree, also probably should be closed other way around as answer here looks much more useful for people new to this (or maybe merged). – Alexei Levenkov Apr 21 '18 at 21:26
  • 1
    @AlexeiLevenkov: I prefer mine because it gets directly to the point, but different folks may prefer different explanation styles. – Ben Voigt Apr 21 '18 at 22:04

1 Answers1

2

Fundamentally, trying to run code which has been built against a library which has later changed in a backwardly-incompatible way is a bad idea. While you may be able to work around it as shown below, I wouldn't be surprised to see quirks in all kinds of places, and the workarounds may become more and more difficult to manage. If at all possible it would be better to take a step back and try to change your dependency management process to avoid this entirely.

Having said which, I can see why it's failing: the JIT compiler isn't able to JIT-compile your method, because it can't find a method reference (get_EnsureText) which is used within the method. It's failing before it even evalutes the if condition.

Here's a small example to demonstrate this. Start with Library.cs:

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

Compile that to Library.dll.

Then write Program.cs:

using System;

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

Note how we're never going to enter the body of the if statement, because it will never be 1000 o'clock.

Compile that, referring to Library.dll.

Next, comment out Account.Name in Library.cs and recompile just Library.dll, then rerun Program.exe:

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

We can work around that by stopping the JIT compiler from ever trying to access the property if it's not going to actually execute it:

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);
    }
}

Go through the same dance as before, and this code now executes without an exception.

Now that wasn't using reflection... but we can change it easily to do so. Change Library.cs to give the account a name by default:

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

Then change Program.cs to use the property only if it's present - but only to even call a method that refers directly to the property if we've checked it:

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}");
    }
}

Going through the same dance as before, but running the code each time, we initially get output of:

Account name: Default name

But after removing the property and recompiling, the output becomes:

Account.Name is missing

... which is what you wanted.

This only works because the JIT compiler is compiling method-by-method though. I don't know of any guarantee that it will do so, instead of speculatively compiling all the methods within a type and failing if any of them has issues. So it's a pretty fragile solution, but it might at least be a temporary workaround for you.

Alternative approach

As mentioned in question comments, you could use dynamic typing to avoid the property reference being embedded directly into the IL. Here's a slightly shorter alternative to the code above:

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");
        }
    }
}

That's certainly briefer, and probably more robust in terms of not relying on JIT-compiler implementation details. On the other hand, it's more prone to typos etc, because the d.Name expression isn't being checked at all at compile-time. (Indeed, the Program.cs code will still compile even against the new version of the library.)

Side-note

This code only tests that there is a property. It could be an initially read/write property but then become write-only, in which case we'll still try to call the get accessor, and fail. The condition you're checking can be modified to handle this reasonably easily though.

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • One of the reasons I do not like Reflection. It just bypasses all of the built in compiliing tools. –  Apr 21 '18 at 21:27
  • Thanks for this great explanation. Learned something today. Hopefully others will find it useful too. – xoail Apr 22 '18 at 00:28