Join us!
Want to join the team?

We would love to get to know you

Are you looking for an employer who cares about you as a person and where you feel involved in everything that concerns you? Welcome to JellyHive!
Who are we? We are a successful IT consultant company and our philosophy is that the company is our employees.
Who are you? You are a system developer (fullstack, frontend, backend), and/or maybe also a scrum master, test lead, devops etc.
What we offer you Participation in developing the company with generous benefits.


Upload your cv Supported filetypes are: .pdf, .doc and .docx
Hide
Stefan Matsson 2018-01-22
# C#  

Can you run code compiled for .NET 4.6.2 on .NET 4.0?

TL;DR: Yes. Yes you can.

If you would have asked me a week ago I would have said ‘No’. That’s also the answer I, incorrectly, gave when this question was asked in a Reddit thread. The question was regarding access to the System.ServiceProcessesServicesController.StartType property (docs) if the framework installed was at least .NET 4.6.1 since the property does not exist in earlier framework versions.

My answer was that this is not possible as the code would not compile if the property being accessed did not exist. But what if the code was compiled with a target framework where the property exist and then ran on a framework where it does not? Can you even do that?

It turns out that you can by changing some values in the supportedRuntime element in app.config.

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <startup> 
        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.2" />
    </startup>
</configuration>

version specifies the .NET Framework major version. All 4.x versions are in-place updates meaning that they will replace any earlier 4.x version installed on the machine. The version inside sku is used as a hint to the .NET loader on which framework version to load the application with. The latest installed version of the framework will always be used if an older version is specified.

If I try to run a program with the above app.config on a machine that does not have .NET 4.6.2 installed I will be asked to install it.

net 462

However if I change the sku version to the latest version installed on my machine

<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0" />

it will gladly run the program as long as I don’t load something that is not supported in the installed framework into the current scope.

net 40 do not run code

If I try load the StartType property into scope the program will crash with a runtime exception.

net 40 run code

Wait… what?

As all 4.x versions are in-place updates they are IL compatible which means that they can all understand the same underlying code format.

So as long as I don’t access the property I’m good? No not exactly. The program will crash when the property that does not exist is loaded into scope. To illustrate I’ll give two code examples.

The first example accesses the property in a different method. This code does not crash.

public class Program
{
    static void Main(string[] args)
    {
        foreach (var service in ServiceController.GetServices().Where(f => f.DisplayName.Contains("Microsoft")))
        {
            var name = service.DisplayName;
            var startupMode = "";
            if (RunCodeThatCrashesOnLowerFrameworks())
            {
                // NOTE: Get StartType from different method
                startupMode = GetServiceStartMode(service);
            }
            else
            {
                startupMode = "Unknown";
            }

            Console.WriteLine("Name : " + name);
            Console.WriteLine("StartMode   : " + startupMode);
            Console.WriteLine("----------------------------------------------------------------------");
        }
        Console.WriteLine("Press key to continue.");
        Console.ReadKey();
    }

    public static bool RunCodeThatCrashesOnLowerFrameworks()
    {
        return false;
    }

    private static String GetServiceStartMode(ServiceController service)
    {
        return service.StartType.ToString();
    }
}

If I inline the property accessor, the program will crash on start.

public class Program
{
    static void Main(string[] args)
    {
        foreach (var service in ServiceController.GetServices().Where(f => f.DisplayName.Contains("Microsoft")))
        {
            var name = service.DisplayName;
            var startupMode = "";
            if (RunCodeThatCrashesOnLowerFrameworks())
            {
                // NOTE: Direct property access in main method
                startupMode = service.StartType.ToString();
            }
            else
            {
                startupMode = "Unknown";
            }

            Console.WriteLine("Name : " + name);
            Console.WriteLine("StartMode   : " + startupMode);
            Console.WriteLine("----------------------------------------------------------------------");
        }

        Console.WriteLine("Press key to continue.");
        Console.ReadKey();
    }

    private static bool RunCodeThatCrashesOnLowerFrameworks()
    {
        return false;
    }
}

Async await

Async await was introduced in C# 5.0 and .NET framework 4.5. Clearly the above trick can’t work if you have async methods?

Actually it does as long as you don’t load the call to the async method into scope. If you do, your program will crash.

net 40 async await no call

net 40 async await call

Conclusion

So production ready and let’s ship it?

You probably should not use this in production unless you know exactly what you are doing. The original question was regarding a single property to know how a Windows service was configured to start. That is probably safe unless your code uses other libraries that use code compiled for a later framework version which does not take this hack into consideration. I would argue that most libraries does not :)

What is needed for this to work?

  • Code compiled for a target framework where the property exist, e.g. 4.6.1.
  • Latest framework installed must be lower than the target framework compiled for, e.g. 4.0 or 4.5.
  • Change the sku version/loader hint to the installed framework version.

The source code is available on our Github: https://github.com/AdviseSolutions/RunCodeFor462On40

Acknowledge

Thanks to /u/Stensborg on Reddit for asking the question and sharing his original POC code.

Comments