2015-08-21 11 views
6

Mam następujący Funkcja (try-catch usunięte):Przełęcz rodzajowo obiekt wpisany do ograniczonej metody rodzajowe VB.NET

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 

który przekształca dany typ do wyliczenia (stąd ograniczenia), jeśli to jest jeden.

To dobrze, ale potem mają inną metodę (uproszczony poniżej), który wykonuje bardziej ogólny casting, i chcę go użyć tej metody, jeśli typ przekazywane w to 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 

Na wezwanie do Enums.ConvertOrDefault, daje błędy:

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' 

Jak mogę powiedzieć „to jest OK, wiem, że to wyliczenia tak jest w porządku”?

--- Edit ---

One (bardzo brzydki) sposób zrobić to w następujący sposób:

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

Ale to ohydne. Z pewnością istnieje sposób, aby to uogólnić?

- Edycja 2: Cel -

Czytam dane z bazy danych Oracle, który przechowuje przycisk (z czego mam ich kilka) Enums jako ciągi; a także przechowywanie innych danych w różnych formatach (Byte() jako RAW, TimeSpan jako IntervalDS itp.). Następnie używam funkcji Convert jako funkcji ogólnej, gdzie, biorąc pod uwagę wynik datareader(column), mogę przekonwertować ten obiekt na odpowiedni typ.

Wszystkie funkcje Oracle.DataAccess.Client.OracleDataReader pobierają indeks, a nie nazwę kolumny; a ponieważ nie mogę zagwarantować kolejności kolumn, i dla zachowania czytelności, użycie nazwy kolumny ma więcej sensu - ale muszę samemu przeanalizować wynik.

Więc mój kod robi coś takiego:

Dim id as Byte() = Convert(dataReader("id_column")) 
Dim something as SomeEnum = Convert(dataReader("somethingCol")) 
'... 

mogę świadomie zadzwonić Enum.ConvertOrDefault zamiast Convert kiedy jestem oczekując Enum, ale to wydaje się złamać zasadę ogólną metodę, która myślę ma więcej sensu ... i pozwoliłaby mi również na ponowne wykorzystanie tej metody w innych kontekstach.

Nadzieję, że pomaga trochę wyjaśnić.

--- Edycja 3 ---

Próbowałem ten pomysł, z komentarzami:

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

i

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 

Ale to daje błędy podczas nazywam Convert metoda dla typów takich jak String lub Byte(), mówiąc:

„Typ argumentu«String»musi mieć publiczny instancję bez parametrów konstruktora, aby zaspokoić«nowych»ograniczenie dla parametru typu«T»

+0

Trochę niechlujnie byłoby dodać 'Enum' do ograniczeń, ale potem wymusić błąd w instancji. To robi coś bardzo podobnego do niepowodzenia ograniczeń. – Paul

+0

Nie jestem pewien, czy rozumiem, przepraszam. Dodać 'Enum' gdzie? – simonalexander2005

+0

Po prostu mnie zignoruj ​​- zapomniałem, że nie możesz dodać Enuma do ograniczeń ...: -/ – Paul

Odpowiedz

1

To działa: D Dzięki! Jeśli napiszesz to jako odpowiedź, możesz otrzymać nagrodę. Szkoda, że ​​nie mogliśmy odpowiedzieć na pytanie w obecnym stanie - "Jak mogę powiedzieć" jest OK, wiem, że to jest Enum, więc jest w porządku "?", Lub, możesz dalej ograniczać T, kiedy przekazujesz go innej metodzie.

Realizację tego, co poleciłem, pokazano poniżej. Próbując wyjaśnić, dlaczego nie możesz zrobić tego, co chcesz, być może to pomoże. Ponieważ w Convert(Of T) i T może reprezentować dowolny typ, kompilator nie może zagwarantować bezpieczeństwa typu podczas przekazywania go do bardziej ograniczonego rodzaju generycznego; istnieje funkcja CType dla typów ogólnych.

Nie rozumiem, dlaczego metoda Enum.TryParse wywołuje ograniczenie Struktury.Patrząc na source code nadal sprawdza właściwość Type.IsEnum, więc wydaje się zbędne, aby spróbować nałożyć ograniczenie częściowe.

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 
3

Można rozważyć zastosowanie innego rodzaju wyliczone zestaw wartości. Być może będziesz mógł zamiast tego używać wzorca wyliczeniowego polymorphic/class/.

Te, których używam, zwykle mają metodę TrySelect, która służy do przeliczania wartości podstawowych na wyliczenie. Można również obsługiwać wiele podstawowych wartości różnych typów dla każdej wartości wyliczeniowej. Na przykład:

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

} 

Następnie można dodać metody do teksty stałe dla bardziej wyspecjalizowanych operacji. Można budować mapy ze swoimi teksty stałe, że mapa kolumny do pozycji indeksu itp Można również łatwo iteracyjne nad zestawem wyliczonych wartości lub wartości leżących u podstaw łatwo stosując jedną z All* właściwości (BoolEnum.AllValues, BoolEnum.AllBooleanValues, BoolEnum.AllTextValues, BoolEnum.AllIntegerValues).

FYI> Nie jest trudno to zaimplementować przy użyciu leków generycznych, dzięki czemu większość blach grzewczych jest WYSUSZONA. Przykład subclassable (zrzeczenie się: to jest mój artykuł na ten temat) opiera się na użyciu ogólnej podstawowej klasy wyliczeniowej.

Oto dotnetfiddle pokazuje powyższy przykład enum w akcji: https://dotnetfiddle.net/O5YY47

Oto wersja VB.Net z powyższym:

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 

A oto dotnetfiddle dla wersji VB.Net : https://dotnetfiddle.net/HeCA5r

1

Używasz VB.NET, języka, który jest już dość przyjazny dynamicznemu pisaniu. Niewiele można zrobić z typowymi ograniczeniami typu na Enums, dosyć twardym ograniczeniem w .NET. Podstawowy problem polega na tym, że typy wyliczeniowe nie mogą zachowywać się w sposób ogólny, a ich rozmiar zależy od konkretnego typu. Który może być 1, 2, 4 lub 8 bajtów, w zależności od rodzaju bazy. To bardzo ważna sprawa dla leków generycznych, a plik cookie-cutter (inaczej MSIL) jest inny.

Po prostu rozwiąż problem, VB.NET dostarcza piłkę, w takim przypadku naprawdę podoba Ci się funkcja pomocnika Conversion.CTypeDynamic(). Potrzebujesz tylko trochę dodatkowego kodu, aby poradzić sobie z obiektami zerowymi i rozróżnianiem wielkości liter. Również powinny rozważyć obsługi DBNull gdy robisz to dla dBase konwersji polowych:

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 

Uwaga drugi szczegół realizacja VB.NET w tym kodzie, nie trzeba new T. Nic nie jest już idealnie wartością dla wyliczenia. I nie musisz rzucać żadnych wyjątków, CTypeDynamic już narzeka z udokumentowaną komunikatem wyjątku, który jest zlokalizowany.