If you have followed the posts about how C/AL really executes in NAV, you know that C# and C/AL can sometimes be in a state where C/AL compiles, but C# does not, causing you some headaches during run time.
However, what might not be obvious is that there are situations where C/AL does not compile anymore (typically due to a changed dependency signature, or due to an object that went AWOL) but C# not only compiles, but also happily runs as if nothing is wrong in the first place.
These situations can be confusing, and after having read my original post, my friend Heinz has pointed out to those situations and asked me if I can explain them. So, here it goes.
Imagine this situation: you create two codeunits, A and B. Then, inside B you write two functions, one of which simply does work inside B, and another one that calls into A. You compile B, then delete A.
What you have at this stage is uncompilable codeunit B. Why cannot you compile it? Because it references A, and A does not exist anymore. However, B was successfully compiled, and its C# generated while A existed. Now A does not exist anymore, so of course you cannot compile B, but having understood how C# is getting created and executed, you’d expect that you cannot run B anymore, because C# that was generated in B is not valid anymore because it also references A that does not exists on C# level either.
However, this is not what you will experience. In fact, your codeunit B will not only be successfully compiled by C# compiler regardless of the fact that there is no C# code available for A anymore, but you’ll also be able to run it until the execution reaches the point where it calls into the non-existent A.
Let’s drop A and B talk, and let me show some real code.
Consider this codeunit:
And now consider this one, that calls into it:
While 87655 exists in the database, you’ll be able to compile 87656. So – compile it. Then, delete 87655 from the database.
And now, try to compile 87656 again. It fails, because:
However, if you run codeunit 87656 at this stage, it will go through the compilation process I explained in From C/AL to executable: how NAV runs your C/AL code. So, you’d expect this C# compilation to fail, because as much as C/AL does not find codeunit 87655, neither should C# be able to do so. However, C# compilation passes, and the object happily runs, as you can figure out yourself by observing this:
Well, it shouldn’t be quite okay, because you have just compiled and ran an object that references another object that does not exist.
Then, you click OK, and a confirmation dialog is shown. If you click No, you’ll get another message that says “Nothing to do”. However, if you click Yes, then (and only then) you get the error you would expect to have occurred at C# compile time:
So, what’s going on here? Why does this behave in this completely unexpected way?
You’ll get your answer if you take a peek at the C# that was generated for codeunit 87656.
That’s not all, obviously, it’s just a small excerpt that corresponds to this C/AL code:
A lot of people have frequently asked me why wouldn’t we program in C# directly, instead of in C/AL, now that C/AL is translated to C#. Well, I hope that you can see the answer to this question from the example above. The NAV C# code is not exactly straightforward. It is not meant to be human-readable. It’s compiled code, and it is compiled in such a way that it uses the underlying NAV runtime framework in ways that are unnatural for humans to read, follow, or perhaps even not necessarily understand.
For those more versed in C# and .NET, it’s full of generics, late and loose bindings, and this is what in fact happens: no object references in C# are hard references. Local variable of Codeunit 87655 type in C/AL is declared simply as NavCodeunitHandle in C#. It’s late-bound to the specific codeunit instance inside the constructor:
But this late binding is also loose binding, because it’s not bound directly to a type, it’s bound to a type that can handle any codeunit: NavCodeunitHandle.
Also, functions don’t get translated to functions, they get translated into subclasses, and overall the resulting C# code is not easily relatable to the originating C/AL code. And it’s okay that it’s not – the resulting IL from your C#, or resulting byte-code from IL will be increasingly more difficult to follow and understand by a human, because they are all compiled code, intended to be run under a specific framework, software (like NAV runtime, and later CLI) or hardware (like processor). That’s why it would be a very bad idea to write C# code directly.
And it’s why this resulting C# code happily compiles and executes: it has no tight dependencies to any objects.
So – deleting an object in C/AL, or changing a function signature, does break C/AL compilation, so you are unable to recompile the depending objects. But if depending objects have already been compiled and their C# generated, this C# is by all means valid under the available scope of C# compilation that happens later, and resulting .NET assembly is by all means executable. It will fail only when (or if) it encounters an actual invocation of a function of the dependency object not existing at the run time.
Now, once you have this situation of non-compilable C/AL that contains compilable C#, you can generate FOB, export that FOB, import it in another system, and objects will still run, but not compile in C/AL. Needless to say, this can cause total mess at runtime.
Apart from the scary story from yesterday, this may be another reason why you may not want to handle FOB, and should prefer using TXT (or deltas) instead. With FOB, you can end up in a situation that C# runs an invalid state of code, while with TXT this cannot happen: if you cannot compile the objects after importing, you cannot use them either.