Pages

mercoledì 31 ottobre 2012

Enum with objects in .NET

I like enums. don't get me wrong, I like more other kind of stuff but i use them quite often during my programming sessions.

The main flaw of the .NET enums is basicly that they accepts just numeric values: this restricts the scenarios where they can be applied. It could be useful using enums to store fixed complex objects.

now we'll see how to fuck the system and piss on the microsoft engineers that don't allow us to create loads of enums with objects.

typical microsoft engineer.
i think i will look like this in the future: how sad my life is.

First thing, Let's create the enum:



Public Enum testEnum

Dog
Cat
Yoshi

End Enum

basic principle is to "decorate" every value of the enum with an attribute that indicates the class of the object related to him: the value "Dog" will be related to an instance of the object "Dog", the value "Cat" will be related to an instance of the object "Cat", the value "Yoshi" will be related to an instance of the superclass "Animal", cause we don't know what the hell it is.

the enum will look like this:



Public Enum testEnum

<ObjValue(GetType(Dog))> _
Dog

<ObjValue(GetType(Cat))> _
Cat

<ObjValue(GetType(Animal))> _
Yoshi

End Enum

Let's create the "ObjValue" attribute that will create the "magic"



<AttributeUsage(AttributeTargets.Field, AllowMultiple:=False)> _
Public Class ObjValue
Inherits System.Attribute
Private _type As Type


Public Shared Function GetVal(Of T)(ByVal val As [Enum]) As T
Return GetVal(val)
End Function

Public Shared Function GetVal(ByVal val As [Enum]) As Object

Dim type As Type = val.GetType()
Dim output As Object

Dim fi As FieldInfo = type.GetField(val.ToString())
Dim attrs() As ObjValue = fi.GetCustomAttributes(GetType(ObjValue), False)
If attrs.Length > 0 Then
output = attrs(0)._type.GetConstructor(New Type() {}).Invoke(New Object())
End If

Return output

End Function

Public Sub New(ByVal type As Type)
_type = type
End Sub

End Class


the only thing to do now to get the value is calling the static function "GetVal" passing the enum value to obtain the instance of the class that belongs to that value.



ObjValue.GetVal(testEnum.Cat)

the class above is very strict: you can choose a class but you can't assign default values on the single instances. another point is that you can't be able to call a custom constructor...as it is, this class suck ass.

so we need to modify the class to allow the user to use other constructor than the default one:



<AttributeUsage(AttributeTargets.Field, AllowMultiple:=False)> _
Public Class ObjValue
Inherits System.Attribute
Private _type As Type
Private _constrArgsTypes() As Type
Private _constrArgs() As Object

Public Shared Function GetVal(Of T)(ByVal val As [Enum]) As T
Return GetVal(val)
End Function

Public Shared Function GetVal(ByVal val As [Enum]) As Object

Dim type As Type = val.GetType()
Dim output As Object

Dim fi As FieldInfo = type.GetField(val.ToString())
Dim attrs() As ObjValue = fi.GetCustomAttributes(GetType(ObjValue), False)
If attrs.Length > 0 Then
output = attrs(0)._type.GetConstructor(attrs(0)._constrArgsTypes).Invoke(attrs(0)._constrArgs)
End If

Return output

End Function

Public Sub New(ByVal type As Type)
Me.New(type, Nothing)
End Sub

Public Sub New(ByVal type As Type, ByVal constrArgs() As Object)
_type = type
If constrArgs Is Nothing Then
_constrArgs = New Object() {}
_constrArgsTypes = Array.CreateInstance(GetType(System.Type), 0)
Else
_constrArgsTypes = Array.CreateInstance(GetType(System.Type), constrArgs.Length)
For i As Integer = 0 To constrArgs.Length - 1
_constrArgsTypes(i) = constrArgs(i).GetType()
Next
_constrArgs = constrArgs
End If
End Sub

End Class


now you can call for each value of the enum different constructors. this will be useful in many cases, for example if we want to create an enum of fixed dates:



Public Enum HistorycDates

<ObjValue(GetType(DateTime), New Object() {1989, 11, 9})> _
BerlinWallFall

<ObjValue(GetType(DateTime), New Object() {1992, 2, 7})> _
MaastrichtTreaty

<ObjValue(GetType(DateTime), New Object() {2001, 9, 11})> _
TwinTowersAttack

End Enum

ObjValue.GetVal(Of DateTime)(HistorycDates.MaastrichtTreaty)

this is not even my final form.

now that's better....but there's still something to fix: everytime we call "GetVal" we istantiate everytime an object: that's bullshit because we will istantiate several time the same objects.

To sort this out we will use a Dictionary in which we will save every object created, so we can use them every time we need them, avoiding instantiate them thousand times with no reasons.



<AttributeUsage(AttributeTargets.Field, AllowMultiple:=False)> _
Public Class ObjValue
Inherits System.Attribute
Private _type As Type
Private _constrArgsTypes() As Type
Private _constrArgs() As Object

Private Shared _dicValues As New Dictionary(Of String, Object)

Private Shared Function GetValueFromDictionary(ByVal valType As Type, ByVal val As [Enum]) As Object
Dim key As String = valType.FullName & "." & val.ToString()
If _dicValues.ContainsKey(key) Then
Return _dicValues(key)
End If
Return Nothing
End Function


Public Shared Function GetVal(Of T)(ByVal val As [Enum]) As T
Return GetVal(val)
End Function

Public Shared Function GetVal(ByVal val As [Enum]) As Object

Dim type As Type = val.GetType()
Dim output As Object = GetValueFromDictionary(type, val)

If output Is Nothing Then
Dim fi As FieldInfo = type.GetField(val.ToString())
Dim attrs() As ObjValue = fi.GetCustomAttributes(GetType(ObjValue), False)
If attrs.Length > 0 Then
output = attrs(0)._type.GetConstructor(attrs(0)._constrArgsTypes).Invoke(attrs(0)._constrArgs)
_dicValues.Add(type.FullName & "." & val.ToString(), output)
End If
End If

Return output

End Function

Public Sub New(ByVal type As Type)
Me.New(type, Nothing)
End Sub

Public Sub New(ByVal type As Type, ByVal constrArgs() As Object)
_type = type
If constrArgs Is Nothing Then
_constrArgs = New Object() {}
_constrArgsTypes = Array.CreateInstance(GetType(System.Type), 0)
Else
_constrArgsTypes = Array.CreateInstance(GetType(System.Type), constrArgs.Length)
For i As Integer = 0 To constrArgs.Length - 1
_constrArgsTypes(i) = constrArgs(i).GetType()
Next
_constrArgs = constrArgs
End If
End Sub

End Class


that's it. enjoy!

HOLD ON!

this class has some flaws as well:

  • the only way to customize the objects is to use arguments on the constructors.
  • only base types can be used on the attributes.

Nessun commento:

Posta un commento