Can I define an assignment operator in vb.net Structure? - vb.net

I am having a structure that is actually a simple byte with more functionality.
I defined it this way:
Structure DeltaTime
Private m_DeltaTime As Byte
Public ReadOnly DeltaTime As Byte
Get
Return m_DeltaTime
End Get
End Property
End Structure
I want to have these two functionalities:
Public Sub Main
Dim x As DeltaTime = 80 'Create a new instance of DeltaTime set to 80
Dim y As New ClassWithDtProperty With { .DeltaTime = 80 }
End Sub
Is there a way to achieve this?
If there would be a way to inherit from Structure I would simply inherit from Byte adding my functionality, basically I just need a byte Structure with custom functionality.
My question is also valid when you want to define your new singleton member value-types (like, you want to define a nibble-type for instance etc.) and you want to be able to set it with an assignment to number or other language typed representation.
In other words, I want to be able to do define the following Int4 (nibble) stucture and use it as follows:
Dim myNibble As Int4 = &HF 'Unsigned

Create a conversion operator, e.g.
Structure DeltaTime
Private m_DeltaTime As Byte
Public ReadOnly Property DeltaTime() As Byte
Get
Return m_DeltaTime
End Get
End Property
Public Shared Widening Operator CType(ByVal value As Byte) As DeltaTime
Return New DeltaTime With {.m_DeltaTime = value}
End Operator
End Structure
UPDATE:
For your proposed Int4 type I strongly suggest that you make it a Narrowing operator instead. This forces the user of your code to cast explicitly, which is a visual hint that the assignment might fail at runtime, e.g.
Dim x As Int4 = CType(&HF, Int4) ' should succeed
Dim y As Int4 = CType(&HFF, Int4) ' should fail with an OverflowException

Related

In a generic VB.NET structure, how do I access its explicitly provided constructor?

First of all, what I want to achieve:
I want to extend a value datatype by providing additional properties, especially to validate ranges provided at declaration time. I want the new datatype to be a value type as well.
Compare with Ada:
subtype Day_Number is Integer range 1 .. 31;
Ideal, but obviously not implementable, would be:
Dim DayNumber As Int64 Range 1 To 31
However, I would be happy with:
Dim DayNumber As RangeInt64(1, 31)
It is of no concern, if initialization takes its time. Once ranges are provided, they are considered to be immutable. The datatype from then on is only used to set/get values like with ordinary value types, only that they are subject of being validated against the initially provided range.
My attempt:
Since I cannot inherit from structures in order to expand on them, I tried to incorporate a structure into a structure as a member.
In a module, I have this structure:
Friend Structure SRangeValueType(Of T)
Private lMinimum As T
Private lMaximum As T
Friend Property Minimum As T
Get
Return lMinimum
End Get
Set(tValue As T)
lMinimum = tValue
End Set
End Property
Friend Property Maximum As T
Get
Return lMaximum
End Get
Set(tValue As T)
lMaximum = tValue
End Set
End Property
Friend Sub New(Minimum As T, Maximum As T)
lMinimum = Minimum
lMaximum = Maximum
End Sub
End Structure
I attempt to use this generic structure as a member of another structure (of concrete type Int64):
Public Structure RangeInt64
Private Range As SRangeValueType(Of Int64)
End Structure
However, this is not using the constructor with the two arguments.
Say I want to initialize Range (the only member of the structure RangeInt64) with the values 100 and 200 for Minimum and Maximum, resp.
I am not allowed to use something like:
Private Range As SRangeValueType(Of Int64)(100,200)
What is the correct syntax to provide my values to the generic constructor?
Normally, if you add a constructor to a structure, you can call it using the New keyword:
Dim x As SRangeValueType(Of Int64) ' Calls the default, infered, parameter-less constructor
Dim y As New SRangeValueType(Of Int64)(100, 200) ' Calls the explicitly defined constructor
However, that's not really the problem. The problem is that you are trying to set the default value of a non-shared field in a structure. That is something which is never allowed. All non-shared fields in structures must default to their default value (i.e. Nothing). For instance:
Public Structure RangeInt64
Private x As Integer = 5 ' Error: Initializers on structure members are valid only for 'Shared' members and constants
Private y As New StringBuilder() ' Error: Non-shared members in a structure cannot be declared 'New'
End Structure
And, as you may already know, you cannot override the default, inferred, parameter-less constructor on a structure either:
Public Structure RangeInt64
Public Sub New() ' Error: Structures cannot declare a non-shared 'Sub New' with no parameters
x = 5
y = New StringBuilder()
End Sub
Private x As Integer
Private y As StringBuilder
End Structure
As such, you are stuck. By design, when the default constructor is used, all fields in the structure must always default to Nothing. However, if you really, really need it to be a structure, and you can't just convert it to a class, and you really need to change it's default value, you could theoretically fake it into working by using a property to wrap the field:
Public Structure RangeInt64
Private _Range As SRangeValueType(Of Int64)
Private _RangeInitialized As Boolean
Private Property Range As SRangeValueType(Of Int64)
Get
If Not _RangeInitialized Then
_Range = New SRangeValueType(Of Int64)(100, 200)
_RangeInitialized = True
End If
Return _Range
End Get
Set(value As SRangeValueType(Of Int64))
_Range = value
End Set
End Property
End Structure
It should go without saying, though, that it's pretty gross and should be avoided if possible.
Update
Now that you've provided more details about what you are trying to accomplish, I think I may have a better solution for you. You're right that structures do not support inheritance, but what they do support is interfaces. So, if all you need is a bunch of range types, one per value-type, all having the same minimum and maximum properties, but all returning different predetermined values, then you could do something like this:
Private Interface IRangeValueType(Of T)
ReadOnly Property Minimum As T
ReadOnly Property Maximum As T
End Interface
Private Structure RangeInt64
Implements IRangeValueType(Of Int64)
Public ReadOnly Property Minimum As Long Implements IRangeValueType(Of Int64).Minimum
Get
Return 100
End Get
End Property
Public ReadOnly Property Maximum As Long Implements IRangeValueType(Of Int64).Maximum
Get
Return 200
End Get
End Property
End Structure

Creating a class and custom objects(or types?) to use when instantiating

I need to create a class. A rather simple one. We'll call the class clsItem, just for the purposes of this question. The values this class has to hold are:
PointX - some X coord for a point
PointY - some Y coord for a point
Type - This would be one of several options (e.g. Computer, Printer, Router, Server)
I'm a little comfortable with creating a class, properties, get/set, and so on just for straightforward values. However, when creating an instance of this class, and filling out the parameters for a new instance, I would like enumerated options to appear (I think I'm looking for enumerations at least).
An instance will ask for (as above) (X coord, Y coord, Type)
Dim NewClass As New clsItem(50, 75, Type.Computer)**
Or Type.Printer, Type.Router....
The actual values of Type.[whatever] can just be a number, I suppose (0,1,2....). But how do I go about setting this up so as I'm actually writing this class I can make a Property that's using this?
There's Dim something As Integer, or As String. So I think I would need
Dim something As HardwareType (or whatever I will name it).
Public Property Type As HardwareType
Would creating a Structure in this class work? Or is there a better way?
EDIT 1
Public Class clsItem
Public Property PointX As Integer
Public Property PointY As Integer
Public Property ItemType As Integer
Sub New(X As Integer, Y As Integer, Type As Integer)
PointX = X
PointY = Y
ItemType = Type
End Sub
End Class
I guess the above could be one way to do this as this only has a to be a rather simple class.
But what I want passed as Type in Sub New, are things like
Type.Computer
Type.Printer
Type.Switch
Type.Router
So an Integer may not be what I actually want. I need to create Type.[something] and set a value to each so I can then iterate through Type in my program when I want to instance this class.
What you are looking for is called a constructor. The constructor is like a method which is always called each time an object of the class is created. If the constructor has parameters, then you must pass arguments to those parameters when you create the object (e.g. New Item(50, 75, HardwareType.Computer)) The name of the constructor in the class is always New:
Public Class Item
Public Sub New(x As Integer, y As Integer, t As HardwareType)
Me.X = x
Me.Y = y
Me.Type = t
End Sub
Public Property X As Integer
Public Property Y As Integer
Public Property [Type] As HardwareType
End Class
To provide a defined list of constant option for a parameter, you need to declare it as an Enum:
Public Enum HardwareType
Computer
Printer
Switch
Router
End Enum

How do I copy Array values to a structure

I would like to to copy that values of an array into a Structure.
Example:
' The Array
Dim Columns(2) As String
' The Structure
Private Structure Fields
Public FName As String
Public LName As String
Public Email As String
End Structure
' I would like to map it like so:
Fields.FName = Columns(0)
Fields.LName = Columns(1)
Fields.Email = Columns(2)
Obviously I could write a function if it was so simple, but really there are over 25 columns and it's a pain to write a function that would map it.
Is there some way to do this?
There really is no simple way that will work in all cases. What you are complaining is too much effort is the only way to guarantee that it will work in all cases.
That said, if you can guarantee that the number of elements in the array matches the number of properties/fields in the structure/class and that they are in the same order and of the same types then you could use Reflection in a loop, e.g.
Private Function Map(source As Object()) As SomeType
Dim result As New SomeType
Dim resultType = result.GetType()
Dim fields = resultType.GetFields()
For i = 0 To source.GetUpperBound(0)
fields(i).SetValue(result, source(i))
Next
Return result
End Function
EDIT:
The code I have provided works as is if SomeType is a class but, as I missed the first time around, not for a structure. The reason is that structures are value types and therefore a copy of the original object is being sent to SetValue, so the field value never gets set on that original object. In theory, to prevent a copy being created, you should be able to simply box the value, i.e. wrap it in an Object reference:
Private Function Map(source As Object()) As SomeType
Dim result As Object = New SomeType
Dim resultType = result.GetType()
Dim fields = resultType.GetFields()
For i = 0 To source.GetUpperBound(0)
fields(i).SetValue(result, source(i))
Next
Return DirectCast(result, SomeType)
End Function
As it turns out though, the VB compiler treats that a little differently than the C# compiler treats the equivalent C# code and it still doesn't work. That's because, in VB, the boxed value gets unboxed before being passed to the method, so a copy is still created. In order to make it work in VB, you need to use a ValueType reference instead of Object:
Private Function Map(source As Object()) As SomeType
Dim result As ValueType = New SomeType
Dim resultType = result.GetType()
Dim fields = resultType.GetFields()
For i = 0 To source.GetUpperBound(0)
fields(i).SetValue(result, source(i))
Next
Return DirectCast(result, SomeType)
End Function

Is it possible to add an interface to an existing type?

I've developed some serialization code for my types. They all share a common interface to facilitate the serialization logic. Would it be possible to also add that interface to some basic types like Integer or String so I could pass one of those basic type values into my serialization logic and have it work? I'm imagining something along the lines of extension methods, but adding an interface rather than a method? I'm sure I could come up with some way to do it using late-binding, but I'd like to avoid that, if possible.
No. It is not possible to extend an existing type to make it implement an interface (short of adding Implements IMyInterface to the top of the code for that type, that is). The closest thing to that would be to create a derived class which adds the interface to the base class. If you override the CType operator you could even make it so values could be seamlessly converted from one type to the other without explicitly casting them. However, since you mentioned String and Integer as the types that you want to extend, that is not even possible. You can't create a new type that inherits from String because String is defined as NotInheritable. Similarly, you can't create a new type that inherits from Integer because Integer is a Structure, not a Class. Structures do not support inheritance.
Therefore, the best option that you have would be to create a new class which wraps the core value, extends it by implementing the interface, and then overrides the CType operator to make it simple to convert between the core type and the wrapper type. For instance, let's say you had an interface like this:
Public Interface IWritable
Sub Write()
End Interface
And you had a method that took an argument of that type, like this:
Private Sub TestWrite(writableObject As IWritable)
writableObject.Write()
End Sub
If you needed to pass an Integer into that method, you could make a wrapper class like this:
Public Class WritableInteger
Implements IWritable
Public Sub New(value As Integer)
Me.Value = value
End Sub
Public Property Value As Integer
Public Sub Write() Implements IWritable.Write
Console.Write(Value)
End Sub
Overloads Shared Widening Operator CType(value As Integer) As WritableInteger
Return New WritableInteger(value)
End Operator
Overloads Shared Widening Operator CType(value As WritableInteger) As Integer
Return value.Value
End Operator
End Class
Since the CType operator is overloaded as Widening, that means that you can convert the value between the two types without casting (even with Option Strict On). For instance, this works:
Dim w As WritableInteger = New WritableInteger(5)
Dim i As Integer = w
w = i
Unfortunately, since TestWrite is asking for an IWritable rather than a WritableInteger, you can't just call TestWrite with an Integer, like this:
Dim i As Integer = 5
TestWrite(5) 'This won't work!
The compiler knows that it needs to convert the Integer to an IWritable object, but since any number of types may implement that interface, it doesn't automatically try to figure out if any of them provide a CType operator for that. Since there may be multiple types that allow widening conversions from Integer to IWritable, it just throws up its hands and cries fowl. Therefore, even though the widening conversion is declared, you still have to explicitly cast the type in a case like that. For instance:
Dim i As Integer = 5
TestWrite(CType(i, WritableInteger))
Or, perhaps more simply:
Dim i As Integer = 5
TestWrite(New WritableInteger(5))
You could make it more convenient by creating overloads for all the common types that will need to be wrapped. For instance, if you created an overload to the TestWrite method, like this:
Public Sub TestWrite(value As Integer)
TestWrite(New WritableInteger(value))
End Sub
Then you could easily call it like this:
TestWrite(5)
Converting back from an IWritable variable to an Integer, though, is even more difficult. For instance:
Dim w As IWritable = New WritableInteger(5)
Dim i As Integer = w ' This won't work!
Dim i2 As Integer = CType(w, Integer) ' Whis won't work either!
If you need to do that, you'd actually have to first cast it to a WritableInteger (and know that it is that type of object in the first place), for instance:
Dim w As IWritable = New WritableInteger(5)
If TypeOf w Is WritableInteger Then
Dim i As Integer = CType(w, WritableInteger)
End If
Unfortunately, there's really no way to make that any easier while still maintaining the safety of the compile-time type checking.
Finally it's also worth mentioning that, if you decide to make a wrapper like that, and the implementation of the interface is the same regardless of the wrapped type, then you could implement it as a generic type, like this:
Public Class Writable(Of T)
Implements IWritable
Public Sub New(value As T)
Me.Value = value
End Sub
Public Property Value As T
Public Sub Write() Implements IWritable.Write
Console.Write(Value)
End Sub
Overloads Shared Widening Operator CType(value As T) As Writable(Of T)
Return New Writable(Of T)(value)
End Operator
Overloads Shared Widening Operator CType(value As Writable(Of T)) As T
Return value.Value
End Operator
End Class
Then you could call the TestWrite method like this:
TestWrite(New Writable(Of Integer)(5))
TestWrite(New Writable(Of String)("Hello World"))

Performance Enum vs class?

i found few new style (for me) to "define" output from select query.
Private Enum Item
ID
Item
Description
End Enum
Private Class Item
Private ID as String
Private Item as String
Private Desc as String
End Class
I 'm thinking of using either one of them. by using class i does not need to re-cast the element type before i display. but Enum seems like easier to understand.
Anyone have some suggestion how to decide?
Enum members are numeric (usually integer, but can be long). But they are not variable and do not change at runtime. So your enum equates to:
Private Enum Item
ID = 0
Item = 1
Description = 2
End Enum
If you want Description to be a string, then a class is a better idea. Enums are used to reference or index something or limit/define a selection. Like:
Public Property Stooge As Stooges
Friend Enum Stooges
Larry
Moe
Curly
Shemp
CurlyJoe
End Enum
The Stooge Property must be one of those values. in code it will show you the text ("moe") but store and integer (1). users will be shown the text in drop downs etc.
You can associate a description with Enum constants:
Public Enum Stooges
<Description("Larry - Funny one")> Larry
<Description("Moe - 'Smart' One")> Moe
<Description("Curly - Sore One")> Curly
<Description("Shemp - One with bad haircut")> Shemp
<Description("CurlyJoe - Last one")> CurlyJoe
End Enum
To get the description for a single one:
Public Shared Function GetDescription(ByVal EnumConstant As [Enum]) As String
Dim fi As Reflection.FieldInfo =
EnumConstant.GetType().GetField(EnumConstant.ToString())
Dim attr() As DescriptionAttribute =
DirectCast(fi.GetCustomAttributes(GetType(DescriptionAttribute),
False), DescriptionAttribute())
If attr.Length > 0 Then
Return attr(0).Description
Else
Return EnumConstant.ToString() ' return enum name if no Descr
End If
End Function
Usage: str = enumHelper.GetDescription(Stooge.Moe) (enumHelper is the name of the calss where the static/shared function resides).
To get a String Array of all the descriptions:
Public Shared Function GetDescriptions(ByVal type As Type) As String()
Dim n As Integer = 0
Dim enumValues As Array
Try
enumValues = [Enum].GetValues(type)
Dim Descr(enumValues.Length - 1) As String
For Each value As [Enum] In enumValues
Descr(n) = GetDescription(value)
n += 1
Next
Return Descr
Catch ex As Exception
MessageBox.Show(ex.Message)
Return Nothing
End Try
End Function
Usage: Dim strEnum As String() = enumHelper.GetDescriptions(GetType(Stooges))
From your question, what you really mean is Struct vs Class. I would default to creating a class. The main reason to use a struct vs a class, is when you need value semantics -- assignment/parameters copies the bits, not a pointer. This is fairly rare in my experience. Unless you have a compelling reason (and you know the difference), go with a class.