Pass generically-typed object into constrained generic method vb.net

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


You are using VB.NET, a language that is already pretty friendly to dynamic typing. There's little you can do with generic type constraints on Enums, a pretty hard limitation in .NET. Core problem is that enum types cannot behave generically, their storage size depends on the specific type. Which can be 1, 2, 4 or 8 bytes, depending on the base type. That's a very big deal to generics, the cookie-cutter (aka MSIL) is different.

So just punt the problem, VB.NET provides the ball, in a case like this you really like the Conversion.CTypeDynamic() helper function. You just need a bit of extra code to deal with null objects and case sensitivity. You also should consider handling DBNull when you do this for dbase field conversions:

Friend Function Convert(Of T)(convertFrom As Object, Optional ignoreCase As Boolean = True) As T
    If convertFrom Is Nothing Then Return Nothing
    If GetType(T) = GetType(DBNull) Then Return Nothing
    If GetType(T).IsEnum Then
        Return CTypeDynamic(Of T)([Enum].Parse(GetType(T), convertFrom.ToString(), ignoreCase))
    Else
        Return CTypeDynamic(Of T)(convertFrom)
    End If
End Function

Note the other VB.NET implementation detail in this code, you don't need new T. Nothing is already a perfectly fine value for an enum. And you don't need to throw any exceptions, CTypeDynamic already complains with a documented exception message that is localized.