2

I'm using the ILNumerics library for a project, but I found that variables declared with "var" will sometimes change value in the middle of computation, for example:

// A is a matrix
var x = A[full, 0];
double xNorm = (double)norm(x);

The x is valid in the first line, however causes "NullReference" exception after execution of the second line. But If I do:

// A is a matrix
ILArray<double> x = A[full, 0];
double xNorm = (double)norm(x);

The computation will be fine. So what is the problem here? Does that mean we need to be cautious when using "var"?

jmcilhinney
  • 50,448
  • 5
  • 26
  • 46
zhengbli
  • 702
  • 5
  • 24
  • 1
    You need to make sure that the compiler's type inference mechanism is inferring exactly what you think it is. I'd check out the actual runtime type of x in both examples and see if they differ, and if so, how. It's also possible the problem is somewhere else entirely, of course. – 15ee8f99-57ff-4f92-890c-b56153 Apr 07 '14 at 02:44
  • It appears to be a base type instead of the exact type. But I don't know why would it change after first line. – zhengbli Apr 07 '14 at 02:46
  • 1
    I think your best course would be to examine GetType().FullName for x in both cases and see what it tells you. When you speak of it "changing after the first line", do you mean after the first example, or after the first line of the first example? The value of x is not changing without an assignment. What's in the stack trace on the NullReferenceException? – 15ee8f99-57ff-4f92-890c-b56153 Apr 07 '14 at 02:50
  • It changes after the second line of the first example. So after the first line, x is "ILNumerics.ILRetArray" with a valid value, but after the second its type is unchanged, while its value becomes "((ILNumerics.ILDenseArray)(x)).DebuggerDisplay' threw an exception of type 'System.NullReferenceException'ILNumerics.ILRetArray". – zhengbli Apr 07 '14 at 03:04
  • 1
    Are you saying you're compiling those four lines of code, just like that, in one scope, in a method? And it compiles with the same identifier declared twice in the same scope? Are you typing "((ILNumerics.ILDenseArray)(x)).DebuggerDisplay" into the watch window? Assuming that you're on a breakpoint here, what happens if you just hover over the mouse over x in the source at that point? – 15ee8f99-57ff-4f92-890c-b56153 Apr 07 '14 at 03:18
  • @AndrewCounts In C#, any class instance is passed by reference, and [these are classes](http://ilnumerics.net/apidoc/html/T_ILNumerics_ILDenseArray_1.htm). – 15ee8f99-57ff-4f92-890c-b56153 Apr 07 '14 at 03:21
  • 1
    @EdPlunkett Thanks for the patience! I attached a short version of the code that can run and reproduce the situation, which can be found in: http://1drv.ms/1e5CNHd – zhengbli Apr 07 '14 at 03:32

2 Answers2

2

It's not a bug - it's a feature :)

The var keyword is not allowed to be used in this context. This is part of a collection of three ILNumerics specific rules. It can also be found in the guickstart guide.

In short, the ILNumerics memory management relies heavily on implicit type conversions. All functions / properties return array types of ILRetArray<T>. These return types are volatile by design: they dispose off their storage after the first use. Always. So one can give them to some other function or query some information from them. But you would rather not try to access it a second time!

However, the only way to access such an object twice would be to have a reference (a regular local variable) around. ILNumerics specifies that all local variables must be of type ILArray, ILLogical or ILCell. Once you assign an array to a variable of one of those types, implicit type conversions kick in and 'turn' the volatile return object into something more stable and safe to access several times.

This is why the var keyword is forbidden in C# with ILNumerics. Similar thing for Visual Basic, where you also must declare the type of local array variables explicitly. I once made a blog post about the issue:

http://ilnumerics.net/blog/why-the-var-keyword-is-not-allowed-in-ilnumerics/

Haymo Kutschbach
  • 3,322
  • 1
  • 17
  • 25
  • That's terrible reasoning (on their part, not yours!). Rather than say "you can't use `var`", which is *just syntactic sugar*, they should just make it clear that the intermediate results can only be used once, and provide a "make permanent" function to allow the final result to be used repeatedly. – Rawling Apr 07 '14 at 07:26
  • Thanks HK. I had a vague premonition it might be something like that. I agree with @Rawling, though, that it's Not My Favorite Design Decision. At the very least, the user should be told just what happened. Seems like a case of premature memory-use optimization. Are they really certain the CLR's garbage collection is worse than their own? Oh, well. It's an interesting way of doing things, and people do seem to find it a useful library. – 15ee8f99-57ff-4f92-890c-b56153 Apr 07 '14 at 11:27
  • @Ed Plunkett Yes we are certain it pays off. The CLR GC is really bad in handling large arrays on the LOH. It is not made for it. And MS recommends to use pooling in such cases. This is what we do: we reuse the memory. The types and their implicit conversions just realize a deterministic disposal. It is needed for an efficient memory pooling. You can easily measure the results: using large arrays without it you'd spend roughly half the time in GC... But the implicit conversions also allow the tracking of memory on many other places. – Haymo Kutschbach Apr 07 '14 at 12:46
  • @Rawling "MakePermanent()" is exactly the assignment to a local variable. – Haymo Kutschbach Apr 07 '14 at 12:47
  • No, "MakePermanent()" is the *implicit type conversion* which occurs when you assign to a local variable *of the correct type*, which specifically is not the type you get if you use `var`. – Rawling Apr 07 '14 at 12:59
  • @HaymoKutschbach I take back the "premature" part! I had no idea about the large array thing with the GC. And I can understand why you want MakePermanent implicit: It's exactly the kind of clutter you're trying to get rid of. I still wish ILRetArray threw a more informative exception, though. – 15ee8f99-57ff-4f92-890c-b56153 Apr 07 '14 at 13:29
  • 1
    Ack & Ack to both of you. There is really much space for improvement for the exception messages. – Haymo Kutschbach Apr 07 '14 at 15:29
1

When declared with var, x is referring to something of type ILRetArray'1; if declared as ILArray<double>, it's ILArray'1. Those are sibling subclasses of a common base (ILDenseArray). A's type is ILArray<double> and its array-indexing operator returns type ILRetArray<ElementType>.

There are implicit conversion operators going both ways here between those two classes.

It looks like when A[full, 0] is assigned to a variable whose type is left up to type inference, the compiler makes it the return type of A's array indexing operator: ILRetArray<ElementType>. But if you declare x's type explicitly as ILArray<double>, an implicit conversion operator is invoked and you get a different actual object.

Then you pass it to norm, which expects ILInArray, and yet another implicit conversion operator is invoked. And there's a bug in that one. It breaks something in the internal state of ILRetArray. Try this line before the call to norm:

var d = (ILInArray<double>)x;

...and it will have the same effect on x as the call to norm did. So it's that conversion.

I haven't downloaded the source to ILNumerics to identify the details of the bug. It's possible that this is meant to be a forbidden operation, in which case I'd wish they had better error reporting (UPDATE see @HaymoKutschbach's answer below: This is in fact the case; ILRetArray is a volatile type by design, intended to be valid only until the first implicit conversion operator is called on it).

It might be fun to submit a bug report to ILNumerics and see what you hear back (UPDATE: There's a guy from ILN in this thread, so never mind).

But in answer to your question: You do need to be a bit cautious when using var; avoid it when there's any possibility of ambiguity. You need to be especially cautious when implicit conversion operators are involved. I had forgotten those things even existed in C#. I'm not fond of them. Of course the bug wasn't caused by their use of an implicit conversion operator; it's just a bug. But the operators vastly and invisibly confused the issue of what was going on in the code. I prefer type conversions to be visible in the source.

Thanks for posting this, it was fun.

  • Thanks for such detailed answer! It would make more sense if the A[full, 0] actually returns different objects in two cases, though which is still beyond my understanding why it would happen. I guess it's most likely to be a bug indeed. – zhengbli Apr 07 '14 at 04:44
  • It's is not a bug - it's a feature ;) See my answer below. – Haymo Kutschbach Apr 07 '14 at 07:13
  • @Lan. It does (see @HaymoKutchbach's answer below) return a new instance of `ILRetArray` each time; it's just that in the non-var case, the language calls a conversion function on the return value "in mid-air" as it were, assigns the result of that conversion function to `x`, and the return value itself from A[,] is never assigned to anything. There's no overload resolution by return type in C#, for reasons which must now be all too obvious. – 15ee8f99-57ff-4f92-890c-b56153 Apr 07 '14 at 11:33