将通用类型的对象传递给约束泛型方法vb.net

时间:2015-08-21 08:59:29

标签: vb.net generics enums

我有以下功能(删除了try-catch):

    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

将给定类型转换为枚举(因此为约束),如果它是一个。

那很好,但我接下来有另一种方法(简化如下)进行更一般的转换,如果传入的类型是枚举,我希望它使用该方法:

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

对于Enums.ConvertOrDefault的调用,这会产生错误:

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'

我怎么能说“没关系,我知道这是一个Enum所以它很好”?

---编辑---

一种(非常丑陋)的方式如下:

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)
         ' ...

但那太可怕了。当然有一种方法可以推广它吗?

- 编辑2:目的 -

我正在从Oracle数据库中读取数据,该数据库将Enums(我有几个)存储为字符串;以及以各种格式存储其他数据(Byte()RAWTimeSpanIntervalDS等。然后我使用Convert函数作为泛型函数,在给定datareader(column)的结果的情况下,我可以将该对象转换为适当的类型。

所有Oracle.DataAccess.Client.OracleDataReader的{​​{1}}函数都使用索引而不是列名;因为我不能保证列的顺序,并且为了便于阅读,使用列名更有意义 - 但是我必须自己解析输出。

所以我的代码就是这样:

.Get...

当我期待Dim id as Byte() = Convert(dataReader("id_column")) Dim something as SomeEnum = Convert(dataReader("somethingCol")) '... 时,我可以故意调用Enum.ConvertOrDefault而不是Convert,但这似乎打破了一般方法的原则,我认为这更有意义。 ..并且还允许我在其他环境中重用该方法。

希望这有助于澄清一点。

---编辑3 ---

我从评论中尝试了这个想法:

Enum

Friend Shared Function Convert(Of T As {New})(value as Object) as T

但是当我为String或Byte()这样的类型调用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 方法时会出现错误,说

  

“类型参数'String'必须具有公共无参数实例   构造函数以满足类型参数“T”

的“新”约束

3 个答案:

答案 0 :(得分:3)

您可能需要考虑使用不同类型的枚举值。您可以改为使用polymorphic / class / subclassable枚举模式。

我使用的方法通常使用TrySelect方法来解析枚举的基础值。此外,您可以为每个枚举值支持不同类型的多个基础值。例如:

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() + "]";
    }

}

然后,您可以在枚举中添加方法,以进行更专业的操作。您可以使用将列映射到索引位置等的枚举来构建地图。您还可以使用All*属性(BoolEnum.AllValues,{{1}轻松地轻松迭代枚举值集或低值}},BoolEnum.AllBooleanValuesBoolEnum.AllTextValues)。

FYI&GT;使用泛型实现这一点并不困难,因此大多数样板都被干掉了。 subclassable示例(免责声明:这是我的文章)基于使用通用基础枚举类。

这是一个dotnetfiddle,显示上面的示例枚举:https://dotnetfiddle.net/O5YY47

这是上面的VB.Net版本:

BoolEnum.AllIntegerValues

以下是VB.Net版本的dotnetfiddle:https://dotnetfiddle.net/HeCA5r

答案 1 :(得分:1)

  

有效:D谢谢!如果你把它写成答案,你可以得到赏金。很遗憾我们无法回答这个问题 - &#34;我怎么能说&#34;它没关系,我知道它是一个Enum所以它&&#34 #39;很好&#34;?&#34;,或者,你可以在将T传递给另一种方法时进一步限制T.

我推荐的实现如下所示。为了解释为什么你不能做你想做的事,也许这会有所帮助。由于在Convert(Of T)T中可以表示任何类型,因此编译器在将其传递给更受约束的泛型类型时无法保证类型安全性;泛型类型有CType函数。

我不明白为什么Enum.TryParse方法调用Structure约束。查看source code它仍会检查Type.IsEnum属性,因此尝试强制部分约束似乎是多余的。

Friend Shared Function Convert(Of T)(value As Object) As T
   If GetType(T).IsEnum Then
      Return ConvertOrDefault(Of T)(value, True)
   Else
      Return DirectCast(value, T)
   End If
End Function

Friend Shared Function ConvertOrDefault(Of TEnum)(convertFrom As Object, ignoreCase As Boolean) As TEnum
   ' Since this function only excepts Enum types, declaring the return value
   ' will initialize it to zero.
   Dim retVal As TEnum
   Dim typeTEnum As System.Type = GetType(TEnum)
   If typeTEnum.IsEnum Then
      Dim convertFromString As String = TryCast(convertFrom, String)
      If convertFrom IsNot Nothing AndAlso convertFromString IsNot Nothing Then
         Try
            retVal = DirectCast(System.Enum.Parse(typeTEnum, convertFromString), TEnum)
         Catch ex As ArgumentNullException
            ' eat it
         Catch ex As ArgumentException
            ' eat it
         Catch ex As OverflowException
            ' eat it
         End Try
      End If
   Else
      Throw New ArgumentException("Type must be enum")
   End If
   Return retVal
End Function

答案 2 :(得分:1)

您正在使用VB.NET,这种语言已经非常适合动态打字。对于Enums的泛型类型约束,你可以做的很少,这在.NET中是一个非常严格的限制。核心问题是枚举类型一般不能表现,它们的存储大小取决于具体类型。其中可以是1,2,4或8个字节,具体取决于基本类型。这对于仿制药而言非常重要,但千篇一律(又名MSIL)是不同的。

所以只是解决问题,VB.NET提供了球,在这样的情况下你真的很喜欢Conversion.CTypeDynamic()辅助函数。您只需要一些额外的代码来处理空对象和区分大小写。在为dbase字段转换执行此操作时,还应考虑处理DBNull:

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

请注意此代码中的其他VB.NET实现细节,您不需要new T。对于枚举来说,没有什么是完美的价值。并且您不需要抛出任何异常,CTypeDynamic已经抱怨已记录的本地化异常消息。

相关问题