Create a new Value data type

EDN Admin

Well-known member
Joined
Aug 7, 2010
Messages
12,794
Location
In the Machine
<br/>
I all <br/>
<br/>
I have a bit of time to kill, so I will write some Bla-Bla here<br/>
<br/>
So let talk about "How to create a new Value data type"<br/>
<br/>
---------------<br/>
<br/>
First a note: The method I will use here is the method used by the Framework to create a Value type (ie Boolean, Int32, Byte, etc...). This method is not only friendly to .. , but also supported by the CLR and any .NET compiler (Otherwise it
wouldnt work!)<br/>
<br/>
---------------<br/>
<br/>
Since Boolean already exist as a bi-Value, I will create here a Tri-Value. Let call it "UpDown".<br/>
<br/>
"UpDown" will be so that it can take 3 possible values: Up, Flat or Down<br/>
<br/>
----------------<br/>
<br/>
The first thing that we need is a class that will let us "Box" the new type we will create.<br/>
<br/>
To create such class, there is only one way it is to use the keyword "Structure" (We will see later why)<br/>
<br/>
Well, I can see that some of you are thinking "A Structure is not a Class"<br/>
<br/>
Well, actualy, yes, a structure is a class that is used to box a value and interact with it<br/>
<br/>
As proof if I use this code:<br/>
<br/>
Public Structure UpDown <br/>
End Structure <br/>
<br/>
Once compiled, I will have<br/>
<br/>
.class public sequencial ansi sealed WindowsApplication.UpDown <br/>
Extends [mscorlib]System.ValueType <br/>
{} <br/>
<br/>
where "[mscorlib]System.ValueType" is declared as:<br/>
<br/>
.class public auto ansi abstract serializable beforefieldinit System.ValueType <br/>
extends System.Object <br/>
<br/>
(It is interresting here to note that even if the class System.ValueType is marked "public" and "abstract" it will be impossible to inherits from it. The VB compiler does make a special case with this class and will not let you do it.) <br/>
(C# compiler does the same)<br/>
<br/>
The keyword "Structure" was made with the only purpose of creating a class that inherits from System.ValueType<br/>
(Note: keyword "Enum" is the only other way to inherits from Value Type)<br/>
<br/>
Now, lets come back to this first step and create this class like this. We will look at the implementation later<br/>
<br/>

<pre class="prettyprint lang-vb" style=" Friend Structure UpDown
End Structure[/code]
<br/>
-----------<br/>
The second step to take is to expose the possible values that this type can take.<br/>
<br/>
In a compiler defined data type, these value are exposed in the compiler assembly, not in the application assembly. <br/>
<br/>
This gives the compiler the possibility to hardcode the values into the compiled code and therefore create a faster runtime.<br/>
<br/>
Here, since it is not a compiler defined value type (And that we dont want to re-write the VB compiler) we will need to use a module to expose these values. It is basicaly the same process, just that the comparaisons are done at runtime
instead than being done at compile time.<br/>
<br/>
So, let for now just create this module without all the needed implementation.<br/>
<br/>

<pre class="prettyprint lang-vb" style=" Friend Module DefUpDown
Public Up as UpDown
Public Flat as UpDown
Public Down as UpDown
End Module[/code]

<br/>
-----------<br/>
<br/>
At this point, before we start to do any implementation, the following things need to be knew.<br/>
<br/>
I have said that we have to use a structure, to create the class that will be used to do the boxing. <br/>
Why ?<br/>
<br/>
The reason is that the created class will inherits from ValueType,... And the functionality of ValueType that is is needed here is that ValueType does overrides the method Equals to perform a bitwise comparaison of the objects instead of comparing
the value of the instance pointer. So, two objects, located at two different location in memory will be considered equals if they are bitwise the same. <br/>
<br/>
Another point that is needed to be noticed here is that a value, when passed as argument, will ALWAYS be boxed<br/>
<br/>
---------<br/>
<br/>
Let look at what we have so far, 3 values, Up, Down and flat. And these 3 values are equal since the comparaison that the assembly does is a bitwise comparaison.<br/>
<br/>
For this to be usefull, need to create a difference between them.<br/>
<br/>
I will here follow the way that was used by the boolean Value to create a difference between True and False and create an internal, private integer property. <br/>
<br/>
The use of Integer as internal type is motivated by the fact that when we will create a new UpDown this internal integer will initialize to zero. and by setting one of the exposed value of UpDown to the pattern "InternalValue = 0" we will create a
default value (This is why, in the Boolean data type, True can be anything, but False has to be zero)<br/>
<br/>
So, let create this private value into the structure<br/>
<br/>
<br/>

<pre class="prettyprint lang-vb" style=" Friend Structure UpDown
Private Property InternalValue As Integer
End Structure[/code]
<br/>
--------<br/>
<br/>
Now, we are facing a problem. <br/>
<br/>
To initialize the internal value of Up, Down and Flat, we need to use reflection, but as I said earlier, when a value is passed as parameter, it is always boxed so using<br/>
<br/>
PropertyInfo.SetValue(Up, 1, Nothing)<br/>
<br/>
will not work, since the class "Up" passed as argument to the method SetValue is the "box" class, not the actual value that we want to changed. Therefore, using SetValue as it is usualy done will only change the internal value in the "box" object, and
as soon the method SetValue will return, the created "box" will get out of scope and the value will be lost (this is because the arguments are boxed, but not unboxed).<br/>
<br/>
To solve the problem, we will have to take the control of the boxing process and explicitly do the boxing/unboxing of the argument <br/>
<br/>
Let see that:<br/>
-We will set the internal value of: Up to 1, Down to -1, Flat to 0 (So flat will be the default value)<br/>
<br/>
-To do the boxing and unboxing, there is more than one way, but "DirectCast" is the most strait forward way)<br/>
<br/>
We can therefore initialized our values this way<br/>
<br/>

<pre class="prettyprint lang-vb" style=" Public Up As UpDown =
New Func(Of UpDown)(Function()
Dim Flags As Integer = 36 NonPublic Or Instance
Dim UD As UpDown
Dim T As Type = UD.GetType
Dim P As Reflection.PropertyInfo = T.GetProperty("Direction", CType(Flags, Reflection.BindingFlags))
Dim Box As ValueType = DirectCast(New UpDown, ValueType) Boxing a UpDown
P.SetValue(Box, 1, Nothing)
Return DirectCast(Box, UpDown) Unboxing
End Function).Invoke

Public Flat As New UpDown

Public Down As UpDown =
New Func(Of UpDown)(Function()
Dim Flags As Integer = 36 NonPublic Or Instance
Dim UD As UpDown
Dim T As Type = UD.GetType
Dim P As Reflection.PropertyInfo = T.GetProperty("Direction", CType(Flags, Reflection.BindingFlags))
Dim Box As ValueType = DirectCast(New UpDown, ValueType) Boxing a UpDown
P.SetValue(Box, -1, Nothing)
Return DirectCast(Box, UpDown) Unboxing
End Function).Invoke[/code]
<br/>
You will note that the value {Flat} do not need any initialization since it is the default value <br/>
<br/>
------------------<br/>
<br/>
At this point, there is only one mandatory step that left to be done, it is to implement the operator = and <><br/>
<br/>
This will be easy since our boxing class inherits from ValueType and therefore, as I said, already contains the <br/>
<br/>
implementation for the required bitwise equality camparaison of the objects.<br/>
<br/>
we can therefore simply do:<br/>
<br/>

<pre class="prettyprint lang-vb" style=" Public Shared Operator =(ByVal First As UpDown, ByVal Second As UpDown) As Boolean
If UpDown.Equals(First, Second) Then Return True
Return False
End Operator

Public Shared Operator <>(ByVal First As UpDown, ByVal Second As UpDown) As Boolean
If UpDown.Equals(First, Second) Then Return False
Return True
End Operator[/code]
<br/>
-----------<br/>
Now, there is 2 more thing that should be done, However not absolutely necessary, it is to implement a CType operator <br/>
<br/>
and a ToString method.<br/>
<br/>
It can be done, as example, like this<br/>
<br/>

<pre class="prettyprint lang-vb" style=" Public Shared Narrowing Operator CType(ByVal First As UpDown) As Integer
Return First.InternalValue
End Operator

Public Overrides Function ToString() As String
Select Case Me.InternalValue
Case 1 : Return "Up"
Case -1 : Return "Down"
End Select
Return "Flat"
End Function[/code]
<br/>
----------<br/>
<br/>
Ok, Let see what we can do now as far as usage of this code<br/>
<br/>
( Well, let me add another operator in the structure so we have something to play with in the usage<br/>
<br/>

<pre class="prettyprint lang-vb" style=" Public Shared Operator &(ByVal First As UpDown, ByVal Second As UpDown) As UpDown
Dim Val As Integer = First.InternalValue + Second.InternalValue
Select Case True
Case Val > 0 : Return Up
Case Val = 0 : Return Flat
Case Val < 0 : Return Down
End Select
End Operator[/code]



<br/>
---------- USAGE----------------------<br/>
<br/>
Let create an UpDown<br/>
<br/>
Dim UpDown1 as updown <br/>
<br/>
Note: Updown1 is a {Flat} since it was not initialized by our code (it is therefore initialized as to be Default value)<br/>
<br/>
Now we can do<br/>
<br/>
UpDown1 = Up <br/>
<br/>
We here have assing the value {Up} to our UpDown (Note: There is no possible way to assign any other value than <br/>
<br/>
{Up}, {Down} or {Flat} to UpDown1, the compiler will not accept it)<br/>
<br/>
Let test the bitwise equality comparer<br/>
<br/>
If UpDown1 = Flat then
... [ evaluate to false ]<br/>
<br/>
Dim UpDown2 as UpDown = Down <br/>
If UpDown1 = Updown2 then . .. [evaluate to false ]<br/>
<br/>
Since in our implementation UpDown can cast to integer and an integer can cast to boolean<br/>
<br/>
If CBool(Up) then . .. [evaluate to true (Boolean only evaluate to false for 0 ( so for {Flat})]<br/>
<br/>
And testing our last operator & that we implemented<br/>
<br/>
<br/>
Dim HiUp as UpDown = Up <br/>
Dim LoDown as UpDown = Down <br/>
Dim U = Up & Down & HiUp & LoDown
[ So, U = {Flat} ]<br/>
<br/>
<br/>
<br/>
-----------------<br/>
<br/>

The Code All together:

<pre class="prettyprint lang-vb" style=" Friend Structure UpDown

Private Property InternalValue As Integer

Public Shared Operator =(ByVal First As UpDown, ByVal Second As UpDown) As Boolean
If UpDown.Equals(First, Second) Then Return True
Return False
End Operator

Public Shared Operator <>(ByVal First As UpDown, ByVal Second As UpDown) As Boolean
If UpDown.Equals(First, Second) Then Return False
Return True
End Operator

Public Shared Narrowing Operator CType(ByVal First As UpDown) As Integer
Return First.InternalValue
End Operator

Public Overrides Function ToString() As String
Select Case Me.InternalValue
Case 1 : Return "Up"
Case -1 : Return "Down"
End Select
Return "Flat"
End Function

Public Shared Operator &(ByVal First As UpDown, ByVal Second As UpDown) As UpDown
Dim Val As Integer = First.InternalValue + Second.InternalValue
Select Case True
Case Val > 0 : Return Up
Case Val = 0 : Return Flat
Case Val < 0 : Return Down
End Select
End Operator

End Structure


Friend Module DefUpDown

Public Up As UpDown =
New Func(Of UpDown)(Function()
Dim Flags As Integer = 36 NonPublic Or Instance
Dim UD As UpDown
Dim T As Type = UD.GetType
Dim P As Reflection.PropertyInfo = T.GetProperty("Direction", CType(Flags, Reflection.BindingFlags))
Dim Box As ValueType = DirectCast(New UpDown, ValueType) Boxing a UpDown
P.SetValue(Box, 1, Nothing)
Return DirectCast(Box, UpDown) Unboxing
End Function).Invoke

Public Flat As New UpDown

Public Down As UpDown =
New Func(Of UpDown)(Function()
Dim Flags As Integer = 36 NonPublic Or Instance
Dim UD As UpDown
Dim T As Type = UD.GetType
Dim P As Reflection.PropertyInfo = T.GetProperty("Direction", CType(Flags, Reflection.BindingFlags))
Dim Box As ValueType = DirectCast(New UpDown, ValueType) Boxing a UpDown
P.SetValue(Box, -1, Nothing)
Return DirectCast(Box, UpDown) Unboxing
End Function).Invoke

End Module[/code]
<br/>

<br/>

View the full article
 
Back
Top