There are three issues that you will encounter when using Essence# that involve CLR types:
- Obtaining a CLR type as a value that can be stored in a variable, passed as a parameter or sent messages;
- Converting a value from one CLR type to another; and
- Creating instances of CLR generic types.
Obtaining A CLR Type As An Essence# Value
The Essence# class System.Type (in the Essence# namespace CLR.System, which corresponds to the .Net namespace System) has many class messages that answer commonly-used CLR types: For example, the expression Type string evaluates to the .Net object that represents the .Net type System.String and the expression Type timeSpan evaluates to the .Net object that represents the .Net type System.TimeSpan. [Note: Those messages are unique to Essence#; the actual .Net class System.Type doesn’t support them. You can add Essence#-specific instance methods or class methods to any .Net class or struct; they just won’t be available outside of Essence#.]
Of course, if you have an instance of the type, you can just send it the message #getType (which is a message to which all .Net objects will respond.) Another way to get a CLR type is to send the message #instanceType to the Essence# class that represents CLR values having that type.
If the CLR type can’t be obtained using one of the techniques explained above, the fallback is to encode the assembly-qualified name of the type in a string, and then send the string the message #asHostSystemType, as shown in the following example:
'System.IO.ErrorEventArgs, System, Version=126.96.36.199, Culture=neutral, PublicKeyToken=b77a5c561934e089' asHostSystemType
In most cases (but not all,) the #asHostSystemType message will fail if the fully-qualified name of the assembly is not provided. Two exceptions to that would be when the type is in the mscorlib assembly or in the EssenceSharp assembly.
CLR Type Conversion
Formally–in other words, in theory–Essence# is dynamically typed: The “type” of an expression is simply the powerset of all messages that can be sent to the expression without causing a method-binding error. And that is also true in practice, in that the compiler permits any message to be sent to any expression, and in the fact that the compiler permits any expression to be assigned to any variable or passed as any argument in any message, and in the fact that typing errors are only discovered and reported at run time.
But that does not mean that any expression can be passed as any argument in any message to any receiver without causing any run-time type errors, even in cases where the only messages that will be sent to the value of an argument won’t result in a method binding error. That constraint does hold in most other Smalltalk implementations, and it does hold when the method that will be invoked by the message is an Essence# method. But it does not hold when the “method” that will be invoked is a CLR method.
In Essence#, a CLR method is just a “user primitive”–although the “primitive” does not always need to be formally defined as a method in the Essence# source code, because the dynamic binding subsystem automatically binds Essence# messages to CLR methods that require less than 2 arguments, to CLR type constructors that require no arguments, and to any non-private properties or fields of a CLR type. And it is generally the case in all Smalltalk implementations that “primitive methods” will fail if the types of their arguments are not what they require–even if the primitive could have performed its intended operation simply by sending (using Smalltalk dynamic message dispatch) the right messages to the argument. Primitives generally don’t send messages to their arguments using Smalltalk dynamic message dispatch. And the methods of CLR types certainly do not.
Fortunately, the Essence# dynamic binding system will–if it can–automatically do the required type conversions for you, such as number type conversions, String/Symbol conversions, and any conversions defined by implicit or explicit conversion operators defined by a CLR type. It will even convert Essence# blocks into CLR functions with the required parameter type signature.
But the Essence# dynamic binding system isn’t magic. It can’t handle all cases. It’s simply not possible to convert any type into any other type. And even when it is, the run time system may not have the information required to do it correctly. And in other cases, the logic to do the required conversion simply hasn’t been implemented, for one reason or another.
CLR Array Types
One common case where a conversion might be possible, but the dynamic binding system doesn’t attempt to do it, involves arrays. From the point of view of the CLR, an Essence# array is an array whose elements have the type System.Object (an instance of the Essence# class Array,) or the type System.Byte (a ByteArray,) or the type System.UInt16 (a HalfWordArray,) or the type System.UInt32 (a WordArray,) or the type System.UInt64 (a LongWordArray,) or the type System.Single (a FloatArray,) or the type System.Double (a DoubleArray,) or the type System.Decimal (a QuadArray) or the type String (a Pathname.)
Unfortunately, there are many methods of CLR types that require an array having elements of some type other than the ones supported by Essence#. If the CLR method parameter requires an array whose element type is not the same as that of the corresponding argument at run time, then the binding to the method will fail.
Fortunately, if the conversion of the array to one with the required type is possible (because all the elements can be converted to the required CLR type,) you can code that conversion yourself in Essence#. The following example shows how (this is not new functionality; it just hasn’t been documented/explained):
| clrSourceArray elementType arrayType argument| "Step 1: Convert the Essence# array into a CLR array:" clrSourceArray := #('one' 'two' 'three') asHostSystemArray. "Step 2: Get the desired element type of the array to be passed as an argument:" elementType := System.Type string. "Step 3: Create a CLR array type with the necessary element type:" arrayType := elementType makeArrayType. "Step 4: Create the CLR array having the correct size and element type:" argument:= arrayType new: clrSourceArray size. "Step 5: Copy the elements of the source array into the array that will be used as an argument:" 1 to: clrSourceArray size do: [:index | argument at: index put: (clrSourceArray at: index)]. ^argument
Or you could just send the message #asHostSystemArrayWithElementType: to the Essence# array, as in the following example:
#('one' 'two' 'three') asHostSystemArrayWithElementType: System.Type string
CLR Generic Types
The CLR has three types of “generic type”: Open generic types, partially-open generic types and closed generic types. An open generic type is also called a generic type definition. An open generic type is one where all of its generic type parameters remain “open” because none of them have been bound to a specific type argument. A partially-open generic type is one where some, but not all, of the type’s generic type parameters have been bound to specific type arguments. A closed generic type is one where all of the type’s generic type parameters have been bound to specific type arguments. For the most part, a partially-open generic type is essentially the same as an open generic type. The distinction that really matters is between closed generic types and ones that have generic type parameters that aren’t bound to a specific type.
It’s possible to create instances of closed generic types, but it is not possible to create instances of open or partially-open generic types. And that can be a problem, because .Net class libraries typically only provide generic type definitions that define open generic types. So, although you can define an Essence# class that represents a .Net type that is an open generic type definition, you won’t be able to use that Essence# class to create instances of the type by sending it the normal instance creation messages (e.g, #new or #new:). Creating instances of such a type requires providing one or more types that will be used as the type arguments to construct a closed generic type from the open generic type defined by the .Net class library.
Fortunately, you can use Essence# to construct a closed generic type from an open generic type definition, as illustrated in the following example:
| openGenericDictTypeDefinition esClass closedGenericType | openGenericDictTypeDefinition := 'System.Collections.Generic.Dictionary`2' asHostSystemType. esClass := openGenericDictTypeDefinition asClass. closedGenericType := esClass instanceTypeWith: Type string with: Type string. dict := closedGenericType new. dict at: #foo ifPresent: [:value | System.Console write: 'The value at #foo is '; writeLine: value ] ifAbsent: [ System.Console writeLine: '#foo is not present' ]. dict at: #foo put: #bar. dict at: #foo ifPresent: [:value | System.Console write: 'The value at #foo is '; writeLine: value ] ifAbsent: [ System.Console writeLine: '#foo is not present' ].
The Essence# class that represents a CLR type can be obtained by sending the message #asClass to the CLR type object.
If an Essence# class represents a generic type (whether the type is open, partially-open or closed makes no difference,) then you can create a closed generic type by sending one of the following messages to the Essence# class: #instanceTypeWith: aCLRType, #instanceTypeWith: aCLRType with: aCLRType, #instanceTypeWith: aCLRType with: aCLRType with: aCLRType , , #instanceTypeWith: aCLRType with: aCLRType with: aCLRType, #instanceTypeWith: aCLRType with: aCLRType with: aCLRType with: aCLRType with: aCLRType or #instanceTypeWithAll: anArrayOfCLRTypes.
One good way to handle the case where the .Net type you want to use is an open generic type definition would be to define a subclass of an Essence# class that represents that type, and then use one of the messages above to construct a closed generic type that will be the instance type of the subclass.
Another good way to do it is simply to use the CLR’s syntax for closed generic types when specifying the instance type of the subclass. The following examples show both approaches:
"Class creation/configuration using the Essence# #instanceTypeWith: message" | superclass class | superclass := 'System.Collections.Generic.List`1, mscorlib' asHostSystemType asClass. class := Class new. class superclass: superclass; instanceType: (superclass instanceTypeWith: System.Type object).
"Class creation/configuration using the CLR's syntax for closed generic types" | class | class := Class new. class assemblyName: 'mscorlib'; hostSystemNamespace: #System.Collections.Generic; hostSystemName: 'List`1[[System.Object]]'