Generics in .NET Interop for NAV 2013

image.NET Framework is full of programming conceptual gems, that are now at the fingertips of us poor C/AL folks. One of those is generics. However, the C/AL support for generics at the first glance seems rather limited, and the help file says that you can’t specify data types, and that all generics will be instantiated with System.Object as their type. However, with Microsoft Dynamics NAV 2013, there is a very simple way which allows you to use generics with other data types, as well. So, if .NET Framework Interoperability interests you a slightest bit, here’s a solution. The example below will be for the System.Collections.Generic.Dictionary<,>, and I will show how to use instances of the Dictionary<,> object with any desired data type, without having to pull in any external assemblies. The Solution Before I show the example, I’ll first explain the solution. Declaratively, you can’t declare a generic variable, and specify the type. C/SIDE is just not (yet) that flexible. But that doesn’t matter, because .NET Framework includes a nice feature which allows you to create instances of any type on the fly: Reflection. By using reflection, you can create an instance of a generic type, and specify which type(s) it should use, all with very little coding. In the examples that follow, I’ll create an instance of Dictionary<string,int>. The whole process in C# would look, more or less, like this:

Dictionary<string, int> dict = Activator.CreateInstance( 
  typeof(Dictionary<,>).MakeGenericType( 
    new Type[] { typeof (string), typeof (int) })) as Dictionary<string, int>;

Okay, it’s kind of hax0rish, because all is inline, so if you prefer it step by step, here it goes:

// Step 1 
Type[] types = new Type[] { typeof (string), typeof (int) };

// Step 2 
Type dictionaryType = typeof (Dictionary<,>).MakeGenericType(types);

// Step 3
Dictionary<string, int> dict = Activator.CreateInstance(dictionaryType) as Dictionary<string, int>;

Step 1 creates an instance of a 2-element array of Type. This is needed for creating a generic type using reflection. Step 2 uses reflection to create a generic type of specified types. The types are specified in the array we created in the step 1. Step 3 uses reflection to create an instance of the type created in step 2. Now that we’ve seen it in C#, let’s map the same to C/AL.

1. Declaration Let’s get it straight, you can’t declare a DotNet variable of a generic type, and specify the actual type (or types) it generalizes. The C/SIDE simply doesn’t allow that. But, don’t worry. Go ahead, and declare the following variables:

Name Subtype
Dict System.Collections.Generic.Dictionary`2.’mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089′
Type System.Type.’mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089′
Activator System.Activator.’mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089′
Arr System.Array.’mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089′
String System.String.’mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089′
Int

Of course, all of the above are DotNet, except for the last one, which is Integer.   2. Creating an instance of an array To declare an instance of an array of type Type, we can call the CreateInstance method of the Array class, by providing the type of System.Type. Then we populate the array with the types of System.String, and System.Int32:

Arr := Arr.CreateInstance(GETDOTNETTYPE(Type),2);
Arr.SetValue(GETDOTNETTYPE(String),0);
Arr.SetValue(GETDOTNETTYPE(Int),1);

This isn’t nearly as elegant as in C#, but accomplishes the goal quite as nicely. Take a note of the GETDOTNETTYPE function – a new gem in C/AL, the equivalent of the typeof keyword in C#.   3. Creating the dictionary type To create the type for the Dictionary<string,int> that we need, we have two steps. First is to get the type of Dictionary, and the second is to use that type to reflect out the actual Dictionary type that we need, based on types specified in the array:

Type := GETDOTNETTYPE(Dict);
Type := Type.MakeGenericType(Arr);

 

Again, it can’t be a single line in C/AL, because the syntax of C/AL does not treat the result of the GETDOTNETTYPE as an object.   4. Creating an actual instance Finally, we create an actual instance of the Dictionary<string,int> object, exactly as we would in C# (except we do it in C/AL):

Dict := Activator.CreateInstance(Type);

There. And now we are ready to use it.   5. Testing if it really is what we need Testing generics is easy – if you pass on to it the arguments of invalid type, they’d complain loudly. So, let’s try to pass some valid ones, and an invalid one:

Dict.Add('first',1);
Dict.Add('second',2);
Dict.Add('third','three');

 

Here, at the 3rd line, it fails with the following message, exactly as expected: This message is for C/AL programmers: A call to System.Collections.Generic.Dictionary`2[System.String,System.Int32].Add failed with this message: The type of one or more arguments does not match the method’s parameter type. So, obviously, we have an actual instance of the Dictionary<string,int> which receives exactly those elements, and behaves exactly as we would expect from a true .NET Framework generic class: if we try to pass a value of incorrect type, it’s not going to be happy.   6. And now for something completely different If you know anything about generics, at this moment you should be puzzled, as I was when I first tried this out. All of the C/AL voodoo above is the equivalent of this code in C#:

Dictionary<object, object> dict = Activator.CreateInstance(
  typeof (Dictionary<,>).MakeGenericType(
    new Type[] { 
      typeof (string), 
      typeof (int) })) as Dictionary<object, object>;

 

No need to try running it, it fails. Actually, it doesn’t fail, it returns null. The problem is, being strongly typed, the .NET runtime can’t cast Dictionary<string,int> as Dictionary<object,object>, and the declared type of Dictionary<object,object> can’t hold a value of Dictionary<string,int>. It’s apples and oranges—even though both are instances of the same generic type, it’s not the same actual type, and they are not typecast compatible. When you declare a DotNet of a generic type, in this case the Dictionary type, it’s declaratively Dictionary<object,object>, so how in the earth did we manage to stuff Dictionary<string,int> into it? As a matter of fact, we never did that. I can’t say for sure, but I’ll give an educated guess here. DotNet is never actually the exact type we declare, but a wrapper class around just about anything. I assume it actually wraps around System.Object, and then uses reflection to access the members of the actual object it wraps. But in the end, why do we care? It does what we need it to do.

Vjeko

Vjeko has been writing code for living since 1995, and he has shared his knowledge and experience in presentations, articles, blogs, and elsewhere since 2002. Hopelessly curious, passionate about technology, avid language learner no matter human or computer.

This Post Has 25 Comments

  1. Eclipses

    Hi,
    thank you for this very interesting post.
    Do you think is possible declare a DataContract in C/AL. Here an example about what I’d like convert in C/AL.

    [DataContract]
    class Person
    {
    [DataMember]
    public string forename { get; set; }
    [DataMember]
    public string surname { get; set; }
    [DataMember]
    public int age { get; set; }
    }

    Thank you
    Eclipses

    1. Vjeko

      @Eclipses: unfortunately, this is not possible to use attributes directly in C/AL – however, you are always able to add attributes to classes in C# and then use those classes from C/AL – all functionality coming from the attribute will still be accessible.

    1. Vjeko

      This is just a demo about how to use generics with correct types, and I used Dictionary because I assume most people are familiar with it. There are plenty of situations where you have to instantiate generics of correct types, and the point of my post is to explain how to do that. Of course it’s possible to use Dictionary (or any other generic type) directly, but then it uses System.Object as all of its generic type parameters, which is why I blogged about this in the first place.

  2. Jorge

    Hi Vjekoslav, Thanks for these resources. I have infact seen your techdays video as well. https://www.youtube.com/watch?v=AgiXkGCdXkE

    But I am facing multiple issues while trying to get this done. I have a .net dll that I created for .net platform with one class and few static functions. It works fine. And I want to use it in NAV! The parameters are double arrays and return values are double values.

    Since the .dll is already registered in the GAC it’s visible to NAV.
    Then I created a Type, Double variable, Array of Double type and set the values to the array. However I am getting the following two errors.

    1. A call to System.Double[].SetValue failed : object cannot be stored in an array of this type

    2. Unable to create an instance of .NET framework object: assembly mydll

    Where am I going wrong here? Could you please give me a heads up as I have combed through most of the forums on NAV, .Net interop posts/topics.

    1. Jorge

      I managed to chase the errors away by registering the .dll using these two commands:

      C:\Windows\Microsoft.NET\Framework\v4.0.30319\

      regasm C:\somefolder\my.dll” /TLB”C:\somefolder\my.tlb” /codebase

      But that’s only on the local pc,local version of NAV. In NAV SQL Server the .net file isn’t still working and giving me the above 2nd error. How can I register this .dll in the NAV server?

  3. Marc

    Hi Vjeko,

    Firstly thanks for the article. In my situation I have a dll which I use to call a NAV web service

    I am trying to move the .NET code to NAV so it is much more accessible, however, I have come across the issue you mention above.

    c# code:
    ———–
    Customer c = new Customer();
    c.No = “1A”;
    c.Name = “Customer A”;
    c.Address = “Address A”;
    c.City = “City A”;

    custList.Add(c);
    CustomerRoot custRoot = new CustomerRoot();
    custRoot.Customer = custList.ToArray();

    CustWS ws = new CustWS();
    ws.UseDefaultCredentials = true;
    ws.Url = ;
    ws.ImportCustomers(custRoot);

    NAV Code
    ————–

    CustomerDN := CustomerDN.Customer();
    CustomerDN.No := ‘1A’;
    CustomerDN.Name := ‘Customer A’;
    CustomerDN.Address := ‘Address A’;
    CustomerDN.City := ‘City A’;

    CustList := CustList.List();
    CustList.Add(CustomerDN);

    CustRoot := CustRoot.CustomerRoot();
    CustRoot.Customer := CustList.ToArray(); //****

    Everything works apart from the starred line – the error “The type of one or more arguments does not match the method’s parameter type.” comes up.

    Is there any way around this ? I have tried lots of things.

    Thanks for any suggestions….

    1. Vjeko

      This is a runtime error, correct? I believe that the problem is that you are trying to assign a object[] (that’s what you get when you instantiate a generic List<> by using the constructor in C/AL, and then call the ToArray method) to a member of type Customer[]. This must fail. Instead of doing CustList := CustList.List(), you should instantiate it using the recipe from this article.

  4. Marc

    You are correct – this is a runtime error, however, maybe my problem is that the CustList is not a generic list?

    In fact it is the definition that comes from NAV when a table is added to an XMLPort parameter. According to the definition in Visual Studio it is the following property:

    private Customer[] customerField;

    My NAV XMLPort is very simple and has the structure:

    CustomerRoot Element Text
    Customer Element Table (Customer)
    No Element Field Customer::No.

    1. Marc

      Sorry for not replying to the original thread – but I can confirm it works! Using the reflection method details above I have been able to reference the objects created in a NAV WS in C/AL with zero code in the DLL. Having all the code maintained in NAV is great. Thanks for your help!

      (I am on the training Thur/Friday so see you then!)

      1. Vjeko

        I’m glad that I helped. And I’m also looking forward to seeing you on Thursday/Friday.

      2. Vyacheslav

        How did you do it?

  5. Jeremy Vyska

    Did the formatting on this post get broken somewhere along the way? The code snippits are looking a little mangled.

    1. Vjeko

      Oh, very likely. I’ve switched the formatting plug in at some point. Let me see if I can fix this. Thanks for pointing this out.

  6. Rahul

    Is it not possible to update a value of a key in a dictionary in CAL, because I don’t see methods. Am I missing something?

    1. Vjeko

      Which method do you not see, and on which type?

  7. Jason Wilder

    I am so thankful for this post! I have been trying to use the C# Amazon API to pull orders into NAV and got stuck on a line of code having to do with a DotNet List. The List was being creating as System.Object but I needed System.String. Your above solution completely solved this for me. Thanks!!!

    1. Vjeko

      Great, I am glad whenever I help 🙂 However, now with AL and extensions “v2” you have List of [Text] type that you can use directly in AL, and it completely replaces the need to have DotNet ones (which don’t work in AL, anyway).

  8. Jason Wilder

    I am stuck on another issue a little different from all the stuff above. I am using the c# provided by Amazon to integrate with NAV. There is a function in there that is expecting a params string[]. Params is a keyword not really a type so how to I declare this type of dotnet variable in NAV?

  9. Raik

    Hi Vjeko,

    I spent several hours to do this with System.Collections.Generic.IEnumerable`1 and a type from the Winscp bibliothek ( IEnumerable )

    Unfortunately I had to give up. Is that even possible?

    C# Code: IEnumerable fileInfos = session.EnumerateRemoteFiles(…)

    https://winscp.net/eng/docs/library_session_enumerateremotefiles

Leave a Reply