I have the following Function (try-catch removed):
Friend Shared Function ConvertOrDefault(Of T As {Structure, IConvertible})(convertFrom As Object, ignoreCase As Boolean) As T
Dim retVal As T
If Not GetType(T).IsEnum Then
Throw New ArgumentException("Type must be enum")
ElseIf convertFrom Is Nothing OrElse Not TypeOf convertFrom Is String Then
Return New T
ElseIf [Enum].TryParse(convertFrom.ToString(), ignoreCase, retVal) Then
Return retVal
Else
Return New T
End If
End Function
Which converts the given type to an enum (hence the constraints), if it is one.
That's fine, but I then have another method (simplified below) that does more general casting, and I want it to use that method if the type passed in is an enum:
Friend Shared Function Convert(Of T)(value as Object) As T
If GetType(T).IsEnum Then
Return Enums.ConvertOrDefault(Of T)(value, True)
Else : return DirectCast(value, T)
End If
End Function
For the call to Enums.ConvertOrDefault, this gives the errors:
Type argument 'T' does not inherit from or implement the constraint type 'System.IConvertible' Type argument 'T' does not satisfy the 'Structure' constraint for type parameter 'T'
How can I say "it's OK, I know it's an Enum so it's fine"?
--- Edit ---
One (very ugly) way to do it is as follows:
Dim type As Type = GetType(T)
If type.IsEnum Then
Select Case type.Name
Case "EnumTypeOne"
Return DirectCast(DirectCast(Enums.ConvertOrDefault(Of EnumTypeOne)(value, True), Object), T)
' ...
But that's hideous. Surely there's a way to generalise that?
-- Edit 2: Purpose --
I'm reading data from an Oracle database, which stores the Enums
(of which I have several) as strings; as well as storing other data in various formats (Byte()
as RAW
, TimeSpan
as IntervalDS
, etc). I then use the Convert
function as a generic function where, given the result of datareader(column)
, I can convert that object into the appropriate type.
All of the Oracle.DataAccess.Client.OracleDataReader
's .Get...
functions take an index rather than a column name; and as I can't guarantee the order of the columns, and for the sake of readability, using the column name makes more sense - but then I have to parse the output myself.
So my code is doing something like:
Dim id as Byte() = Convert(dataReader("id_column"))
Dim something as SomeEnum = Convert(dataReader("somethingCol"))
'...
I could deliberately call Enum.ConvertOrDefault
instead of Convert
when I'm expecting an Enum
, but that seems to break the principle of a general method, which I think makes more sense... and would also allow me to reuse that method in other contexts.
Hope that helps clarify a bit.
--- Edit 3 ---
I tried this idea, from the comments:
Friend Shared Function Convert(Of T As {New})(value as Object) as T
and
Friend Shared Function ConvertOrDefault(Of T As{New}) convertFrom As Object, ignoreCase As Boolean) As T
If Not GetType(T).IsEnum Then
Throw New ArgumentException("Type must be enum")
ElseIf convertFrom Is Nothing OrElse Not TypeOf convertFrom Is String Then
Return New T
End If
Try
Return CType([Enum].Parse(GetType(T), convertFrom.ToString(), ignoreCase), T)
Catch ex As Exception
End Try
' default
Return New T
End Function
But this gives errors when I call the Convert
method for types like String or Byte(), saying
"Type argument 'String' must have a public parameterless instance constructor to satisfy the 'New' constraint for type parameter 'T'
You may want to consider using a different kind of enumerated set of values. You may be able to use the polymorphic/class/subclassable enum pattern instead.
The ones I use usually have a TrySelect
method for resolving underlying values to the enum. Also, you can support multiple underlying values of different types for each enum value. For example:
public class BoolEnum
{
private static Dictionary<bool, BoolEnum> allValuesByNaturalValue = new Dictionary<bool, BoolEnum>();
private static Dictionary<string, BoolEnum> allValuesByTextValue = new Dictionary<string, BoolEnum>();
private static Dictionary<int, BoolEnum> allValuesByInteger = new Dictionary<int, BoolEnum>();
private string boolText;
private int integerValue;
private bool naturalValue;
public static readonly BoolEnum True = new BoolEnum(true, "Is True", 1);
public static readonly BoolEnum False = new BoolEnum(false, "Is False", 0);
private BoolEnum(bool naturalValue, string boolText, int integerValue)
{
this.naturalValue = naturalValue;
this.boolText = boolText;
this.integerValue = integerValue;
allValuesByNaturalValue.Add(naturalValue, this);
allValuesByTextValue.Add(boolText, this);
allValuesByInteger.Add(integerValue, this);
}
public static BoolEnum TrySelect(bool naturalValue, BoolEnum defaultValue)
{
BoolEnum returnValue;
if (allValuesByNaturalValue.TryGetValue(naturalValue, out returnValue)) return returnValue;
return defaultValue;
}
public static BoolEnum TrySelect(string boolText, BoolEnum defaultValue)
{
BoolEnum returnValue;
if (allValuesByTextValue.TryGetValue(boolText, out returnValue)) return returnValue;
return defaultValue;
}
public static BoolEnum TrySelect(int integerValue, BoolEnum defaultValue)
{
BoolEnum returnValue;
if (allValuesByInteger.TryGetValue(integerValue, out returnValue)) return returnValue;
return defaultValue;
}
public static implicit operator bool(BoolEnum boolEnum)
{
return boolEnum != null ? boolEnum.naturalValue : false;
}
public static implicit operator string(BoolEnum boolEnum)
{
return boolEnum != null ? boolEnum.boolText : "Is False";
}
public static implicit operator int(BoolEnum boolEnum)
{
return boolEnum != null ? boolEnum.integerValue : 0;
}
public bool NaturalValue { get { return this.naturalValue; } }
public string BoolText { get { return this.boolText; } }
public int IntegerValue { get { return this.integerValue; } }
public static IReadOnlyCollection<BoolEnum> AllValues { get { return allValuesByNaturalValue.Values.ToList().AsReadOnly(); } }
public static IReadOnlyCollection<bool> AllBooleanValues { get { return allValuesByNaturalValue.Keys.ToList().AsReadOnly(); } }
public static IReadOnlyCollection<string> AllTextValues { get { return allValuesByTextValue.Keys.ToList().AsReadOnly(); } }
public static IReadOnlyCollection<int> AllIntegerValues { get { return allValuesByInteger.Keys.ToList().AsReadOnly(); } }
public override string ToString()
{
return "[" + this.naturalValue.ToString() + ", \"" + this.boolText.ToString() + "\", " + this.integerValue.ToString() + "]";
}
}
Then you can add methods to your enums for more specialized operations. You could build maps with your enums that map column to index position, etc. You can also easily iterate over the set of enumerated values or underly values easily using one the All*
properties (BoolEnum.AllValues
, BoolEnum.AllBooleanValues
, BoolEnum.AllTextValues
, BoolEnum.AllIntegerValues
).
FYI> It is not that difficult to implement this using generics so that most of the boilerplate is DRYed away. The subclassable example (disclaimer: that is my article on this) is based on the use of a generic base enum class.
Here is a dotnetfiddle showing the above example enum in action: https://dotnetfiddle.net/O5YY47
Here is a VB.Net version of the above:
Public Class BoolEnum
Private Shared allValuesByNaturalValue As New Dictionary(Of Boolean, BoolEnum)()
Private Shared allValuesByTextValue As New Dictionary(Of String, BoolEnum)()
Private Shared allValuesByInteger As New Dictionary(Of Integer, BoolEnum)()
Private m_boolText As String
Private m_integerValue As Integer
Private m_naturalValue As Boolean
Public Shared ReadOnly [True] As New BoolEnum(True, "Is True", 1)
Public Shared ReadOnly [False] As New BoolEnum(False, "Is False", 0)
Private Sub New(naturalValue As Boolean, boolText As String, integerValue As Integer)
Me.m_naturalValue = naturalValue
Me.m_boolText = boolText
Me.m_integerValue = integerValue
allValuesByNaturalValue.Add(naturalValue, Me)
allValuesByTextValue.Add(boolText, Me)
allValuesByInteger.Add(integerValue, Me)
End Sub
Public Shared Function TrySelect(naturalValue As Boolean, defaultValue As BoolEnum) As BoolEnum
Dim returnValue As BoolEnum
If allValuesByNaturalValue.TryGetValue(naturalValue, returnValue) Then
Return returnValue
End If
Return defaultValue
End Function
Public Shared Function TrySelect(boolText As String, defaultValue As BoolEnum) As BoolEnum
Dim returnValue As BoolEnum
If allValuesByTextValue.TryGetValue(boolText, returnValue) Then
Return returnValue
End If
Return defaultValue
End Function
Public Shared Function TrySelect(integerValue As Integer, defaultValue As BoolEnum) As BoolEnum
Dim returnValue As BoolEnum
If allValuesByInteger.TryGetValue(integerValue, returnValue) Then
Return returnValue
End If
Return defaultValue
End Function
Public Shared Widening Operator CType(boolEnum As BoolEnum) As Boolean
Return If(boolEnum IsNot Nothing, boolEnum.naturalValue, False)
End Operator
Public Shared Widening Operator CType(boolEnum As BoolEnum) As String
Return If(boolEnum IsNot Nothing, boolEnum.boolText, "Is False")
End Operator
Public Shared Widening Operator CType(boolEnum As BoolEnum) As Integer
Return If(boolEnum IsNot Nothing, boolEnum.integerValue, 0)
End Operator
Public ReadOnly Property NaturalValue() As Boolean
Get
Return Me.m_naturalValue
End Get
End Property
Public ReadOnly Property BoolText() As String
Get
Return Me.m_boolText
End Get
End Property
Public ReadOnly Property IntegerValue() As Integer
Get
Return Me.m_integerValue
End Get
End Property
Public Shared ReadOnly Property AllValues() As IReadOnlyCollection(Of BoolEnum)
Get
Return allValuesByNaturalValue.Values.ToList().AsReadOnly()
End Get
End Property
Public Shared ReadOnly Property AllBooleanValues() As IReadOnlyCollection(Of Boolean)
Get
Return allValuesByNaturalValue.Keys.ToList().AsReadOnly()
End Get
End Property
Public Shared ReadOnly Property AllTextValues() As IReadOnlyCollection(Of String)
Get
Return allValuesByTextValue.Keys.ToList().AsReadOnly()
End Get
End Property
Public Shared ReadOnly Property AllIntegerValues() As IReadOnlyCollection(Of Integer)
Get
Return allValuesByInteger.Keys.ToList().AsReadOnly()
End Get
End Property
Public Overrides Function ToString() As String
Return "[" + Me.m_naturalValue.ToString() + ", """ + Me.m_boolText.ToString() + """, " + Me.m_integerValue.ToString() + "]"
End Function
End Class
And here is the dotnetfiddle for the VB.Net version: https://dotnetfiddle.net/HeCA5r
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With