How To Obtain Type for Generic Conversion

C

Charles Law

I want to do something like this:

obj = CType(value, Value.Type)

Well, not exactly, but I think that captures the essence. I realise it won't
work as I have written it, and it looks a bit like a nebulous statement, but
I am looking for a generic way to convert a variable of unknown type to its
actual type.

Perhaps a better example would be

Enum MyEnum As Byte
AnElement
AnotherElement
End Enum

Dim e As MyEnum
Dim obj as Object

e = AnotherElement

obj = CType(e, Byte)

The thing is, I don't know, until runtime what the underlying type is.

Any thoughts?

TIA

Charles
 
C

Cor Ligthert

Hi Charles,

Can you not do something with overloaded functions: That much types are not
there and maybe even less when you know what you use.

function charles(byval item as integer) as integer
end function

function charles(byval item as string) as string
end function

I did not try it, just something that came up and maybe it helps you.

Cor
 
C

Charles Law

Hi Cor

I think, from what you are suggesting, that the compiler would have to know
at compile time what the type was, so that it could use the correct
overloaded function. In fact, the type won't be known until runtime.

What I am actually doing, is using reflection to translate a structure into
a byte array. As I iterate through the structure - which I am passed at
runtime - each element may have a different type, and I wish to convert each
type into the correct number of bytes for its internal storage.

Charles
 
J

Jay B. Harlow [MVP - Outlook]

Charles,
Have you tried:

Dim e As MyEnum = MyEnum.AnotherElement
Dim obj as Object

Dim theType As Type = GetType(Byte)

obj = Convert.ChangeType(value, theType)

Hope this helps
Jay
 
C

Charles Law

Hi Jay

Thanks. I have tried it now, but it seems to work only for byte types.
Anything larger throws an exception "Value was either too large or too small
for an unsigned byte".

The code I currently have is below:

<code>
Public Shared Function ToByteArray(ByVal MyStruct As Object) As Byte()

' Convert a structure into a byte array

Dim al As ArrayList

Dim Fields As FieldInfo()

Try
Fields = MyStruct.GetType.GetFields

al = New ArrayList

For Each fld As FieldInfo In Fields
If fld.FieldType.Equals(GetType(Byte)) Then
' Add byte to array list
al.Add(CByte(fld.GetValue(MyStruct)))

ElseIf fld.FieldType.Equals(GetType(Int16)) Then
' Add 16-bit value to array list
Dim i16 As Int16

i16 = CType(fld.GetValue(MyStruct), Int16)

' Little-endian
al.Add(CByte(i16 And &HFF))
al.Add(CByte((i16 And &HFF00) >> 8))

' Big-endian
'al.Add(CByte(i16 >> 8))
'al.Add(CByte(i16 And &HFF))

ElseIf fld.FieldType.Equals(GetType(SomeEnumeratedValue))
Then
' This is coded as a byte
al.Add(CByte(fld.GetValue(MyStruct)))

ElseIf fld.FieldType.Equals(GetType(SomethingElse)) Then
' This is coded as a byte
al.Add(CByte(fld.GetValue(MyStruct)))

Else
Throw New Exception("Cannot convert type " &
fld.FieldType.ToString)
End If
Next fld

Catch ex As Exception
Throw

End Try

Return DirectCast(al.ToArray(GetType(Byte)), Byte())

End Function
</code>

As you will see, there is the potential for a lot of If .. Then ... Elses. I
am looking for a generic way to convert to save all the typing and
duplication. Any thoughts?

Charles
 
J

Jay B. Harlow [MVP - Outlook]

Charles,
Thanks. I have tried it now, but it seems to work only for byte types.
Anything larger throws an exception "Value was either too large or too small
for an unsigned byte".

The code I gave IS for byte types! Notice the GetType(Byte) in there, you
would need to change GetType(Byte) to the specific type you are interested
in (for example FieldInfo.FieldType). Remember an Enum can be Byte, Short,
Integer or Long.
Dim theType As Type = GetType(Integer) ' convert to integer
Dim theType As Type = GetType(String) ' convert to string
Dim theType As Type = GetType(Double) ' convert to double
Unfortunately it does not really make sense to convert an integer to an
integer, as I don't see that really buys you anything!

What are you really attempting to do???

Have you looked at BinarySerialization?


I would use a MemoryStream & BinaryWriter if I really needed to implement
the ToByteArray routine. Something like:

' Convert a structure into a byte array
Public Shared Function ToByteArray(ByVal MyStruct As Object) As Byte()
Dim stream As New MemoryStream
Dim writer As New BinaryWriter(stream)
Dim fields() As FieldInfo = MyStruct.GetType().GetFields()
For Each fld As FieldInfo In fields
If fld.FieldType Is GetType(Byte) Then
writer.Write(DirectCast(fld.GetValue(MyStruct), Byte))
ElseIf fld.FieldType Is GetType(Short) Then
writer.Write(DirectCast(fld.GetValue(MyStruct), Short))
ElseIf fld.FieldType Is GetType(Integer) Then
writer.Write(DirectCast(fld.GetValue(MyStruct), Integer))
ElseIf fld.FieldType Is GetType(String) Then
writer.Write(DirectCast(fld.GetValue(MyStruct), String))
ElseIf fld.FieldType.IsEnum Then
' find the underlying type of enum and write that.
Else
' get all the fields on the Structure or Class fields
writer.Write(ToByteArray(fld.GetValue(MyStruct)))
End If
Next fld

Return stream.GetBuffer()
End Function

Alternatively you can use BitConverter.GetBytes to get an array of bytes
given a value.

Note both BinaryWriter & GetBytes don't allow you to work with an object
variable directly.

Hope this helps
Jay
 
C

Charles Law

Hi Jay

I have several structures that represent message packets. I want to be able
to take any one of these structures and convert it into a byte array so I
can send it down the line. Thus, I would have a single routine that could
take any of these structures and send it.

I don't want to have a series of If.... Thens because the structure could
contain a more complex type. I am only interested in the byte-by-byte
representation of the data in the structure. It is perhaps complicated by
the fact that multi-byte types need to be sent little-endian.

Charles
 
J

Jay B. Harlow [MVP - Outlook]

Charles,
The only way out of the "series of If" is to create a collection of objects
that convert individual types to Bytes. I would put these objects in a
HashTable that I use Type as the key to the HashTable.
As you need to have the specific value to convert it from a value to an
array of Bytes. However! I would probably use the if over this method...

Something like (unchecked):

Public MustInherit Class Converter

Public MustOverride Function GetBytes(obj As Object) As Byte()

End Class

' Consider making this a Singleton
Public Class IntegerConverter
Inherits Converter

Public Overriders Function GetBytes(obj As Object) As Bytes()
Return BitConverter.GetBytes(DirectCast(obj, Integer))
End Function

End Class

' Consider making this a Singleton
Public Class ByteConverter
Inherits Converter

Public Overriders Function GetBytes(obj As Object) As Bytes()
Return BitConverter.GetBytes(DirectCast(obj, Byte))
End Function

End Class


Dim converters As New HashTable
converters.Add(GetType(Byte), New ByteConverter)
converters.Add(GetType(Integer), New IntegerConverter)

Dim stream As New MemoryStream
Dim writer As New BinaryWriter(stream)
Dim fields() As FieldInfo = MyStruct.GetType().GetFields()
For Each fld As FieldInfo In fields
Dim converter As Converter = converters.Item(fld.FieldType)
writer.Write(converter.GetBytes(fld.GetValue(MyStruct)))
Next

For nested structures you can simply use Recursion, based on if there is a
predefined Converter or not (converters.Item returned Nothing).


You do realize that you CANNOT rely on the order of the fields returned by
Type.GetFields! The order will change in .NET 2.0!!! My understanding is it
was changed as its more efficient to return the fields in no particular
order, then ensuring they are in a specific order!

I don't have the reference handy, I understand that there is a class
similiar to BitConverter that will ensure that types are little-endian as
opposed to big endian.


Alternatively one or more of the members of the
System.RunTime.InteropService.Marshal class may be useful. For example
Marshal.StructureToPtr followed by series (loop) of Marshal.ReadByte calls.


Personally (Ultimately) for this problem I use a BinaryWriter & BinaryReader
and read & write individual fields to ensure that the fields are written in
the correct order & the correct sizes. I would not attempt to generalize
this to a single routine, that relies on undocumented behavior (the order of
GetFields).

The following article offers using a BinaryReader & BinaryWriter to
serializing a class to a NetworkStream, you can use a FileStream and have
the same effect.

http://msdn.microsoft.com/library/default.asp?url=/library/en-s/dncscol/html/csharp09182003.asp

The example is in C#, however it should be easily converted to VB.NET, post
if you need help.

Hope this helps
Jay
 
C

Charles Law

Jay

Thanks for your continued help. I was feeling all snug and warm reading your
response, as it looked like a nice, neat answer. But then came the
bombshell: I can't rely on the order of fields returned by GetFields!

I would have hoped that if I apply a layout attribute to my structure then
GetFields would return them in the order specified (if you are listening
Microsoft, please tell me this is so).

Anyway, thanks for the info, and it certainly does help.

Cheers.

Charles
 
J

Jay B. Harlow [MVP - Outlook]

Charles,
I was wondering about the layout attribute myself when I was typing my
response.

As the Marshal class needs to "ensure" that the fields are in a specific
order.

My understanding is that .NET 1.0 & 1.1 (VS.NET 2002 & VS.NET 2003) the
order is the order in your source (although its not really documented that I
know of). I seem to remember the only place I remember reading about the
order was Adam Nathan's book ".NET and COM - The Complete Interoperability
Guide" by SAMS press for details.


I don't have the reference, I understand that this order changes in .NET
2.0. Not sure how the Marshal class deals with this or the layout attribute
impacts it. However .NET 2.0 is in preview/alpha, not yet Beta, so obviously
things can change...

Jay
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top