Array<T> as Class Member
In some cases you might want to store arrays in classes. Let's consider the following naive approach first:
If we run this...
...we get an exception. Why?
ILNumerics® Arrays as Class Attributes
ILNumerics arrays can be used as class attributes as long as the following rules are met:
- The attribute must be declared as readonly Array<T>.
- Initialization of the attribute must utilize a special function: ILMath.localMember<T>().
- Assignments to the local variable must utilize the Array<T>.a property (.Assign() function in VB)
- Classes with local array members should implement the IDisposable interface.
By applying the rules 1..3, the corrected example displays:
This time, we get, as expected:
Don't worry if you don't understand all of the details yet! This scheme will work out fine when blindly followed.
Detailed Explanation
1. The attribute must be declared as readonly Array<T>.
Why should we use anything else than a local array, right? But what is the readonly specifier for? It prevents us from accidentally assigning values to the array. Read on in order to understand, why this is important!
2. Initialization of those types must utilize a special function: ILMath.localMember<T>.
A fundamental mechanism of the ILNumerics memory management is related to the lifetime associated with array types. All functions return temporary arrays (RetArray<T>) which only live for exactly one use. After the first use, they will automatically be disposed. In order to make use of such arrays multiple times one needs to assign them to a local variable. This is, where they are converted and the underlying storage is efficiently taken over to a local, persistent array variable.
At the same time, we need to make sure, the array is released after the current ILNumerics scope (using (Scope.Enter())) { … }) was left. For this purpose, the conversion to a local array is used. Since there will be a new array out there, we track the new array for later disposal in the current scope. This tracking is done during the conversion to a persistent local Array<T>.
If a current scope exists and once it is left, it does exactly what it promised: it disposes all arrays created within its body. Now, local array members require a different handling. They commonly live for the lifetime of the class – not of the current ILNumerics scope. In order to prevent local arrays from being disposed after leaving the scope inside the function body, we need something else.
The ILMath.localMember() function does not return a temporary array, but a local array. In fact, the function is quite simple. All it does is create a new Array<T> and return it. Since the types of both sides of the assignment now match, no conversion occurs. The new array is not registered in the current scope, hence it will not be disposed – exactly what we need!
There are other arrays than Array<T>. What if we need a logical array or a cell as class member?
The above code shows how to initialize Array, Logical arrays and Cell arrays in a class context. Now, how do we assign the return value from any function to the local array? This is where the next rule kicks in:
3. Assignments to the local variable must utilize the .a property.
Assigning to a local array directly would activate the disposal mechanism described above. Hence, in order to prevent this for a longer living class attribute, a variable has to be assigned a value by using the .a property. In Visual Basic, the .Assign() function does the same. This will prevent the array from getting registered in the scope.
4. Classes with local array members should implement the IDisposable interface.
Now, that we achieved that our local array attribute is not disposed magically, we – for the sake of completeness – should make sure, it is disposed somewhere. The recommended way of disposing things in .NET is … the IDisposable interface. In fact, for most scenarios, IDisposable is not necessary since we still have the GC in the back. But we recommend implementing IDisposable, since it makes a lot of things more consistent, robust and potentially even faster. However, we provide the IDisposable interface for convenience only – we do not rely on it like we would for the disposal of unmanaged resources. Therefore, a simplified version is sufficient here and one can omit the implementation of a finalizer method for the class in most cases.
Properties and Array<t>
Having Array<T> as an attribute is certainly nice. Even nicer is the capability of providing the data stored in the array as property to users of the class. Here is how it's done:
- Declare a property of the type RetArray<T>.
- In the get {} accessor, simply return a clone of the local attribute: return myLocal.C.
- In the set {} accessor, again assign the local attribute a value using Array<T>.a
Note that changes to the array returned will not change the attribute stored in the class! Rather a lazy clone is returned and the data will be copied on the first attempt to modify the returned array. So both arrays are not attached – the property implements the same 'Value Semantic' scheme like all functions in ILNumerics do.
Example ILNumerics® Array Utilization Class
Here comes a full featured class example with all rules implemented. The property initializes the array in a lazy fashion: the array will be created at the first request only. Since we cannot test on null, we simply test, if the array is empty. Also, because this is what ILMath.localMember<T> creates.
For the user of your class, this yields two big advantages: one can – without knowing the details – clean up its storage easily. And one can be sure that no side effects occur when working with data coming from or going to the class.
Utilizing Array & Co. in User Controls
In the above example code you may have noticed the existence of another subtlety: All properties returning a type from ILNumerics are marked with the DesignerSerializationVisibility attribute:
The utilization of the attribute prevents the following problems from emerging. It must be mentioned that those are not only specific issues concerning ILNumerics but rather exist for all types of strongly named assemblies used as public properties on user controls in the context of the Visual Studio Designer.
- When your class is a control / user control and is opened in the designer, the designer will create default values for all public properties of the control.
- Those values are stored in the *.resx file of the form and incorporated into the InitializeComponents() method in the *.Designer.cs file.
- Since the types exposed originate from a strongly typed assembly, a fully qualified assembly reference is stored with the value. This reference goes right into the *.resx resource file as a base64 encoded string.
- The problems start as soon you get an update for your strongly typed assembly (here: ILNumerics.dll). While it is easy to replace the assembly reference in your project, the reference embedded into the resx file remains! As a consequence, the designer refuses to open those forms / controls, which still point to the old assembly.
In order to prevent those issues from happening, simply use the DesignerSerializationVisibility attribute with a Visibility value of Hidden! It is important to not forget to include the attribute before the control is opened in the designer for the first time. Otherwise, it will be required to manually undo the changes made by the designer.