Nulls in foundation classes? A chance for you to help out.


The name “Axapta Foundation Classes” or just AFC for short,  is given to a number of classes that are used to contain other values. Each one specifies the type of the constituent element.  The foundation classes have many things going for them: They are easy to use and very effective; because there is very little overhead they scale well, both in terms of memory consumption and speed. They are used pervasively in the application code. I will present the individual classes and then ask the burning question where I need your help.


 


They main classes are:


 


Struct


These are name / value pairs that can be built and queried at runtime. They can be very useful for a number of situations where a class might otherwise be used. The example below creates a struct with two fields, adds a values and retrieves them:


 

{
   struct s = new struct(‘int age; str name’);

   s.Value(‘age’, 12);
   s.Value(‘name’, ‘John Doe’);

   print strFmt(‘%1 : %2 years of age’, s.Value(‘name’), s.Value(‘age’));

   pause;
}


Array


Arrays are (as the name implies) arrays of elements that are accessed by index. They grow as required, leaving default values of the given type for hitherto unused elements. They’re very useful because they can contain reference types (i.e. object instances and tables), which normal X++ arrays cannot. They can also easily be passed as parameters and be returned as return values from methods, something that also eludes the normal X++ arrays.


 

{
   array a = new array(Types::integer); // Array of ints
   int i;

   for (i = 1; i < 100; i++)
   {
       a.Value(i, 2*i);
   }

   print CalculateSum(a); // Pass as parameter
}

 


List


Lists are simply a sequence of objects. List elements can be inserted at the start or at the end of the list. There is no ordering of the elements other than the order in which they were inserted.


 

{
  list names = new list(types::string);// List of strings

  names.AddStart(“Jones”);
  names.AddStart(“Smith”);
  names.AddEnd(“Sellers”);
}

Sets
Sets are used to contain elements in such a way that there is only a single element of a given value at a given time. It is also guaranteed that traversal of the set using enumerators and iterators will return the elements in order, smaller before larger.


{
   set uniqueInts = new set(types::integer);
   SetEnumerator se;

   uniqueInts.Add(11);
   uniqueInts.Add(5);
   uniqueInts.Add(45);
   uniqueInts.Add(11); // Already there.

   se = uniqueInts.GetEnumerator();

   while (se.moveNext())
   {
       print se.current();
   }
   pause;
}


Maps
Maps are structures that map one value onto another. People using the .NET framework will know these as SortedDictionary<TDomain, TRange>. As for sets, they can be traversed with enumerators and will always return smaller values before larger ones. Operations are available to look a value in the range set given a domain value, and to check whether any given value exists in the domain.

{
   Map intToString = new Map(types::integer, types::string);
   
   intToString.insert(1, “One”);
   intToString.insert(2, “Two”);

   if (intToString.exists(1))
   {
       print intToString.Lookup(1);
   }
   pause;
}


I have deliberatly not gone into enumerators and iterators, because they are not the issue in this blog. You can find more information about the container classes and how they can be used in http://msdn.microsoft.com/en-us/library/ms941640.aspx.


 


Now, the question that I need your help on is this: The AFC classes as mentioned above accept NULL values to be inserted into them. For instance, if you are maintaining a set of query objects, then it is perfectly legal to insert NULL into such a set:


 

{
   Set qs = new Set(Types::Class);
   Query q = null;
   
   qs.Add(new Query());
   qs.Add(new Query());
   qs.Add(q); // Perfectly legal.
   
   print qs.elements(); // == 3
   pause;
}


The set will now contain three elements, two queries and a null value. The value is a bona fide value: Traversing the set will return the null value just as any other value. Along a similar vein, it is possible to map null onto something in a map, to have list and array elements that are null.


 


It makes good sense to me that it should be possible to insert NULL into individual array elements, and even into a particular list element. Also, setting a value in a struct to null does not seem offensive. However, I have reservations when it comes to storing nulls in sets and maps. The fact that this is possible is not based on a particular user scenario that we wanted to support back in the days where this was implemented: It is merely a result of the implementation that we happened to choose at the time.


 


This behavior is now hurting us in some work that we want to do in the implementation of sets and maps. It is difficult and expensive for us to maintain this behavior given some changes we want to make. The question, then, is this: Is anyone actually relying on this behavior in their application code? The .NET collection classes do not support this behavior and will immediately throw an exception if this is attempted. We are tempted to do the same.


 


Please take the time to provide feedback: It will make a difference in how we proceed with this. Ping pvillads with your opinion


 


Thanks


 


 


Comments (6)

  1. Jorisdg says:

    I have no issue not allowing null in a set. Not allowing null in a map value is up for debate.

    I’ve definitely seen it done, and I’m thinking I’ve probably done it myself (assign nulls to map values).

    But I’d vote for progress. Having done quite some .NET interop within X++ recently, I can only cheer on getting X++ more in line with .NET!

    I will gladly check and change my code where necessary.

  2. JanKjeldsen says:

    Disallowing nulls in set and map key values should not be a heartbreaking issue. But a map value should be able to be anything including null.

    This is consistent with C# SortedList, see my link.

  3. JesperJensen says:

    I use Sets and Maps quite often, but I have never used them to store Null values.

    In the rare case where I might need to store a "Null" value I could use the "Null Object" pattern.

    I welcome the change.

  4. agrippine says:

    I entirely agree with JanKjeldsen: "But a map value should be able to be anything including null."

  5. Søren Rasmussen says:

    Nulls in maps definetely makes sense; but it is not critical for us to have the possibility.

  6. Thank you all for the feedback. I was not crisp: We are not looking to take away the possibility of mapping something TO null, only mapping FROM null. So

    map.insert(NULL,…) is bad, but

    map.insert(…, NULL) is fine.