1

Consider this simple console application:

class Program
{
    static void Main(string[] args)
    {
        var human = CreateHuman(args[0]);
        Console.WriteLine("Created Human");
        Console.ReadLine();
    }

    public static object CreateHuman(string association)
    {
        object human = null;
        if (association == "is-a")
        {
            human = new IsAHuman();
        }
        else
        {
            human = new HasAHuman();
        }
        return human;
    }
}

public class IsAHuman : Human
{
}

public class HasAHuman
{
    public Human Human { get; set; }
}

The Human class is in another assembly, say HumanAssembly.dll. If HumanAssembly.dll exists in the bin directory of our console app, everything would be fine. And as we might expect, by removing it we encounter FileNotFoundException.

I don't understand this part though. Comment human = new IsAHuman(); line, recompile and remove HumanAssembly.dll. Console app won't throw any exception in this case.

My guess is that CLR compiler differentiates between is a and has a associations. In other words, CLR tries to find out and understand and probably load all the types existing in the class definition statement, but it can instantiate a class without knowing what's inside it. But I'm not sure about my interpretation.

I fail to find a good explanation. What is the explanation for this behavior?

Nathan Arthur
  • 8,287
  • 7
  • 55
  • 80
Saeed Neamati
  • 35,341
  • 41
  • 136
  • 188
  • The dll is not loaded until it is actually used in your code. – Andrew Savinykh Aug 05 '14 at 10:40
  • @zespri, that's true. But what do we mean by "used" in our code. From the point of CLR, I need a more robust explanation. Thank you. – Saeed Neamati Aug 05 '14 at 10:41
  • Used means information from this dll is required to execute code. If you comment out the line you mentioned no code path is leading inside the dll. – Andrew Savinykh Aug 05 '14 at 10:42
  • Then what about the second line? If you comment the `IsAHuman` instantiation line, you still have the `HasAHuman` instantiation line, which would be executed without any problem. – Saeed Neamati Aug 05 '14 at 10:44
  • Sure, but you don't need an instance of Human to instantiate HasAHuman. Try accessing the Human property and you'll see the exception again. – Andrew Savinykh Aug 05 '14 at 10:46
  • Can you provide detailed, CIL and CLR explanation? Does "used" mean to execute one instruction of **CIL**? – Saeed Neamati Aug 05 '14 at 10:51
  • Sorry, typing from iPad otherwise would write an answer. Clr loads an assembly when jit cannot compile a method without loading the assembly. – Andrew Savinykh Aug 05 '14 at 10:55

2 Answers2

4

You are seeing the behavior of the JIT compiler. Just In Time. A method doesn't get compiled until the last possible moment, just before it is called. Since you removed the need to actually construct a Human object, there is no code path left that forces the jitter to load the assembly. So your program won't crash.

The last remaining reference to Human is the HashAHuman.Human property. You don't use it.

Predicting when the jitter is going to need to load an assembly is not that straight-forward in practice. It gets pretty difficult to reason through when you run the Release build of your code. That normally enables the optimizer that's built into the jitter, one of its core optimization strategies is to inline a method. To do that, it needs access to the method before it is called. You'd need an extra level of indirection, an extra method that has the [MethodImpl(MethodImplOptions.NoInlining)] attribute to stop it from having a peek. That gets to be a bit off into the deep end, always consider a plug-in architecture first, something like MEF.

Community
  • 1
  • 1
Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
  • then how do you explain this: change `human = new IsAHuman();` to `IsAHuman tempVariable = null;`. Still you get error even though you haven't called a constructor method on that object. – Saeed Neamati Aug 05 '14 at 11:29
  • You did call the constructor, you used the *new* operator. If you don't write a constructor yourself then the C# compiler auto-generates one. And it in turn always calls the constructor of the base class, Human.ctor(). Kaboom. Easy to see if you look at the generated assembly with `ildasm.exe` – Hans Passant Aug 05 '14 at 11:42
1

Here is great explanation of what you are looking for.

The CLR Loader

Specially in the following lines -

This policy of loading types (and assemblies and modules) on demand means that parts of a program that are not used are never brought into memory. It also means that a running application will often see new assemblies and modules loaded over time as the types contained in those files are needed during execution. If this is not the behavior you want, you have two options. One is to simply declare hidden static fields of the types you want to guarantee are loaded when your type is loaded. The other is to interact with the loader explicitly.

As the Bold line says, if you code does not execute a specific line then the types won't be loaded, even if the code is not commented out.

Here is also a similar answer that you might also be interested in -

How are DLLs loaded by the CLR?

Community
  • 1
  • 1
brainless coder
  • 6,310
  • 1
  • 20
  • 36