Alpha Five v11 .NET Support - XBasic, Iterators and Generics – Oh My!

Blog



Alpha Five v11 .NET Support - XBasic, Iterators and Generics – Oh My!


Support for creating and using objects compiled for the Microsoft .NET™ environment was introduced in Alpha Five version 11.  As the .NET environment has matured, a number of interesting features have been added, some that are directly supported in Alpha Five's XBasic language and some that can be used with a little "help" so to speak.

Iterators

XBasic has "for each" operator that works very nicely with .NET classes.  .NET supports an interface called IEnumerable that includes functions to iterate of the elements of a collection or list.  This interface is implemented in any way desired, but must return the current object and allow you to cycle through each of the elements.  If a .NET object supports the IEnumerable interface, then you can use code like this in XBasic to access the elements:
             for each Element in MyCollection
Display(Element.SomeProperty)
next
In this example, the variable Element is updated for each element in MyCollection and the code you write in between the (aptly named) "for each" and "next" statements is executed for each element in the collection.

Generics

Generics are a feature of .NET that is similar to C++ templates.  The idea is that a developer can create a function or class without thinking about the type of element being operated on and use it in many places.  .NET has a set of collection objects that can be created to hold a specific type of object.

XBasic doesn't support generics directly, but does have a way of creating a .NET generic and then using it.  Before we can create an instance of a generic type though, we need to be able to tell the .NET runtime what exact type we want.  For example, the C# below defines a generic dictionary object that is keyed on a string and contains any Object type.
System.Collections.Generic.Dictionary<System.String, System.Object>
But the C# compiler has to translate the name above into another format for use at runtime. At the Common Language Runtime (CLR) level (the .NET runtime), that type looks more like this (note the "`2" in the definition):
System.Collections.Generic.Dictionary`2[System.String,System.Object]

Getting at Generics from XBasic

There is a type in XBasic called DOTNET::Services that has a number of helpful functions.  One of those functions is ConstructGenericTypeName() and it can be used to format the type name for a generic.  Another is CreateObject(), and as you might guess, it can create an object from a type name expressed as a string.
Do create an object that implements a .NET generic like the one above, we would use the following XBasic:
dim sv as DOTNET::Services
dim d as P
dim TypeName as C
TypeName = sv.ConstructGenericTypeName( "System.Collections.Generic.Dictionary",
"System.String",
"System.Object")
sv.CreateObject(d,, TypeName)

Although we could have used some shortcuts in accomplishing this, the code above makes the process a little easier to follow.

  1. First we dim the .NET service object as type DOTNET::Services.
  2. Next we dim a variable to receive the object with the generic type.  We can't declare the type directly in XBasic, but that's okay, we don't need to.
  3. We create the full type name from the collection type ("Dictionary") and the key ("String") and value ("Object") types respectively using the services function ConstructGenericTypeName().
  4. Lastly, we create the object by passing the result object variable and the newly formatted type name to the services function CreateObject().

Putting it All Together

Okay let's do something useful with our newfound knowledge!  Well, one thing we could do is try our hand at creating a parser for JSON (JavaScript Object Notation) a data exchange format used extensively in web applications.  Yes, XBasic has a JSON parser function, but let's pretend it isn't there and create our own.

It turns out that the parsing isn't really that hard.  We can use the JavaScriptSerializer class from the .NET runtime libraries to parse our JSON string.  ParseJSON() simply passes a string to the DeserializeObject() function and returns the result.  Notice that we are registering the System.Web.Extensions namespace in XBasic.  This namespace is where the JavaScriptSerializer type is defined.
Function ParseJSON as P ()
dim sv as dotnet::services
dim asm as dotnet::AssemblyReference

asm.filename = ""
asm.Name="System.Web.Extensions"
asm.Version = "4.0.0.0"
asm.Culture = "neutral"
asm.PublicKeyToken = "31bf3856ad364e35"

sv.RegisterAssembly("::", asm)

dim ser as System::Web::Script::Serialization::JavaScriptSerializer
JSONText = "{\"Name\":\"Apple\"," + \
"\"Expiry\":\"/Date(1230440400000-0500)/\","+\
"\"Price\":3.99," +\
"\"Sizes\":[\"Small\",\"Medium\",\"Large\"]}"

o = Ser.DeserializeObject(JSONText)
ParseJSON = o
end function
To test our parsed JSON, we'll create a function that will display the data in the object we create.  That function is called DumpDictionary() and it knows how to format an object so we can see how it nests.  Notice that there are two nested loops that use the "for each" construct to iterate over the collection and then the keys values for each unique key.
function DumpDictionary as C (Object as P, NestLevel = 0)
Dim Result as C
on error goto oops
for each Element in Object
dim Key as C = Element.Key

Result = Result + Tabs(NestLevel) + Key
if TypeOf(Element.Value) == "P"
if Element.Value.GetType().IsArray
Result = Result + crlf() + tabs(NestLevel + 1) + "["
for each Item in Element.Value
Result = Result + Item + " "
next
Result = Result + "]"
else if Left(Element.GetType().ToString(), \
len("System.Collections.Generic.Dictionary")) \
= "System.Collections.Generic.Dictionary"
Result = Result + \crlf() + tabs(NestLevel + 1) + "[" + \DumpDictionary(Element.Value, NestLevel + 1) + crlf() + \
tabs(NestLevel + 1) + "]"
else
Result = Result + tabs(1) + Element.Value
end if
else
Result = Result + tabs(1) + Element.Value
end if
Result = Result + crlf()
next
goto done

oops:
Result = error_text_get() + "Type" + Object.GetType().ToString()
done:
DumpDictionary = Result
end function
To test our display function, we first create a generic dictionary as discussed above.
dim sv as dotnet::Services
dim d as P
if sv.CreateObject(d, sv.ConstructGenericTypeName("System.Collections.Generic.Dictionary",
"System.String", "System.Object"))

d.Add("Hello", 12.3)
d.Add("Bob", 27.3)

Result = DumpDictionary(d)
showvar(Result, "Simple Dictionary Contents")

else
ShowVar(sv.CallResult.Text, "Error Creating Object");
end if
You should see something like the following displayed in Alpha Five:
Hello   12.3
Bob 27.3
Now let's test the JSON parser.
o = ParseJSON()
Result = DumpDictionary(o)
ShowVar(Result, "Parsed JSON Object")
And here we should see the following:
Name   Apple
Expiry /Date(1230440400000-0500)/
Price 3.99
Sizes [Small Medium Large ]
Wow! There's a lot in there!  Well, yes there is.  And that's an accurate description of the work we've put in to making Alpha Five work with .NET.  We are always working with customers to make sure we have as complete support for .NET from within XBasic as we can.  That includes such things as generation of WCF proxies from WSDL and calling into the web services, ADO.NET (a new SQL driver), and integration of assemblies you write or already use.

The Complete Script

' Create a dictionary and dump it out
dim sv as dotnet::Services
dim d as P
if sv.CreateObject(d, sv.ConstructGenericTypeName("System.Collections.Generic.Dictionary", \"System.String", \"System.Object"))

d.Add("Hello", 12.3)
d.Add("Bob", 27.3)

Result = DumpDictionary(d)
showvar(Result, "Simple Dictionary Contents")

else
ShowVar(sv.CallResult.Text, "Error Creating Object");
end if

' Now try it on the JSON Parser
o = ParseJSON()
Result = DumpDictionary(o)
ShowVar(Result, "Parsed JSON Object")

function DumpDictionary as C (Object as P, NestLevel = 0)
Dim Result as C
on error goto oops
for each Element in Object
dim Key as C = Element.Key

Result = Result + Tabs(NestLevel) + Key
if TypeOf(Element.Value) == "P"
if Element.Value.GetType().IsArray
Result = Result + crlf() + tabs(NestLevel + 1) + "["
for each Item in Element.Value
Result = Result + Item + " "
next
Result = Result + "]"
else if Left(Element.GetType().ToString(), \
len("System.Collections.Generic.Dictionary")) \
= "System.Collections.Generic.Dictionary"
Result = Result + \crlf() + tabs(NestLevel + 1) + "[" + \
DumpDictionary(Element.Value, NestLevel + 1) + crlf() + \
tabs(NestLevel + 1) + "]"
else
Result = Result + tabs(1) + Element.Value
end if
else
Result = Result + tabs(1) + Element.Value
end if
Result = Result + crlf()
next
goto done

oops:
Result = error_text_get() + "Type" + Object.GetType().ToString()
done:
DumpDictionary = Result
end function

function Tabs as C (Count as N)
Dim Result as C
if (Count > 0)
Tabs = replicate(chr(9), Count)
end if
end Function

Function ParseJSON as P ()
dim sv as dotnet::services
dim asm as dotnet::AssemblyReference

asm.filename = ""
asm.Name="System.Web.Extensions"
asm.Version = "4.0.0.0"
asm.Culture = "neutral"
asm.PublicKeyToken = "31bf3856ad364e35"

sv.RegisterAssembly("::", asm)

dim ser as System::Web::Script::Serialization::JavaScriptSerializer
JSONText = "{\"Name\":\"Apple\"," + \
"\"Expiry\":\"/Date(1230440400000-0500)/\","+\
"\"Price\":3.99," +\
"\"Sizes\":[\"Small\",\"Medium\",\"Large\"]}"

o = Ser.DeserializeObject(JSONText)
ParseJSON = o
end function



Mobile has arrived but Web Application Development is Hotter Than Ever
Custom Application Development - A Small Change Makes a Big Difference

About Author

Default Author Image
Chris Conroy

Chris Conroy runs digital programs for Alpha Software.

Related Posts
Role-Based Security for Business Apps
Role-Based Security for Business Apps
Evaluating Low Code Mobile App Development Platforms
Evaluating Low Code Mobile App Development Platforms
Building Business Apps with Flexible Design
Building Business Apps with Flexible Design

Comment

Subscribe To Blog

Subscribe to Email Updates