The other week I attended the UK PowerShot Workshop hosted at Microsoft HQ in Reading and run by the guys from Cloud Ready Software (Dynamics NAV’s very own Gang of 4: Waldo, Gary, Vjeko and Mark Brummel – Mark wasn’t there though). Waldo has posted about where it was and what was covered – suffice to say, it was great and I learnt a lot of stuff (and even more stuff that I put on my “need-to-play-with” list)… This blog post is not going to cover all that.
What this blog is about is a tiny thing that Waldo brought up where a .NET interop feature seems to be impossible to use in C/AL. You can read about the scenario and the problem (search for “predicate”) here. I was confused about this as I didn’t know how a predicate would stop NAV compiling.
So what is a “predicate”? A predicate is basically a function which takes a specific type that returns a boolean (http://www.dotnetperls.com/predicate) but in .NET you normally are talking about predicate delegates which are just pointers to that function (https://msdn.microsoft.com/en-us/library/bfcke1bz(v=vs.110)).
This didn’t really help me to understand.
Let’s dive into the object, in this case PSObject and the property in question has the type PSMemberInfoCollection which has the following Syntax. The words “where T” give the answer of how predicates are involved, this means that this collection has a Constraint on it’s Type (see MSDN reference).
C# [DefaultMemberAttribute("Item")] public abstract class PSMemberInfoCollection<T> : IEnumerable<T>, IEnumerable where T : PSMemberInfo
Why does this complain when you try to compile it in NAV? This is what you get when you try and reference that property.
The first error tells you what is going wrong. At compile time NAV is trying to use System.Object where .NET has a constraint for <T>. Ignore the left-hand side of that C/AL line by the way – it doesn’t matter what you are assigning to (this is a whole other interesting casting issue which will have to be another blog post…), or even if you aren’t assigning it to anything, you just cannot type PSObject.Properties in C/AL or the compiler will complain.
Why is it doing that? I don’t know. Look at Vjeko’s comments on using generics (i.e. anything .NET with <T> in its definition) and what is still missing from .NET Interop and you might start to get some ideas…
The thing is, we know that the code will actually run in the .NET runtime so should work – it’s just the compile that is complaining. So we need to get to the property without letting the compiler know that’s what we are doing. How do we do that? Reflection.
C/AL Type := GETDOTNETTYPE(PSObject); IEnumerable := Type.GetProperty('Properties').GetValue(PSObject);
Note that I am using IEnumerable on the left hand side as NAV will still complain if I try to use the real type of PSMemberInfoCollection (again you need to be careful with this “cast” as it doesn’t necessarily work how you expect it to – next blog…).
Then I spent quite a lot of time trying to use the .ToArray() function to fit in with Waldo’s solution but it’s a “extension method” (#3 in Vjeko’s missing list) from LINQ so I couldn’t get it to work. Eventually I went back to basics and just looped through it (PSPropertyInfo[n] is a DotNet variable with 9999 Dimensions in C/SIDE – basically it’s a buffer for the eventual DotNet array, which is basically what you get if you look behind the scenes of how .ToArray() works)
C/AL n := 0; IEnumerator := IEnumerable.GetEnumerator(); WHILE (IEnumerator.MoveNext()) DO BEGIN n += 1; PSPropertyInfo[n] := IEnumerator.Current(); END; CLEAR(IEnumerator); PsPropertyInfoArray := PsPropertyInfoArray.CreateInstance(GETDOTNETTYPE(PSPropertyInfo),n); FOR i := 1 TO n DO BEGIN PsPropertyInfoArray.SetValue(PSPropertyInfo[i], i - 1); END;
This seems to work well. 🙂
I’m not saying it should replace Waldo’s trick with creating a wrapper at runtime (I love that trick!!) but it’s another option.