2015-08-21 4 views
6

Ho la seguente funzione (try catch rimosso):Passo genericamente tipizzato in vincolato metodo generico 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 

che converte il tipo specificato un'enumerazione (quindi i vincoli), se è uno.

va bene, ma poi ho un altro metodo (semplificato sotto) che fa di fusione più generale, e lo voglio utilizzare questo metodo se il tipo passato è un 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 

Per la chiamata a Enums.ConvertOrDefault, questo dà gli errori:

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' 

Come faccio a dire "va bene, so che è un Enum quindi va bene"?

--- --- Modifica

Un modo (molto brutta) per farlo è la seguente:

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

Ma questo è orribile. Sicuramente c'è un modo per generalizzare questo?

- Modifica 2: Destinazione d'uso -

sto leggendo i dati da un database Oracle, che memorizza l'Enums (di cui ho diverse) come stringhe; così come la memorizzazione di altri dati in vari formati (Byte() come RAW, TimeSpan come IntervalDS, ecc.). Quindi utilizzo la funzione Convert come una funzione generica in cui, dato il risultato di datareader(column), posso convertire quell'oggetto nel tipo appropriato.

Tutte le funzioni .Get... di Oracle.DataAccess.Client.OracleDataReader prendono un indice anziché un nome di colonna; e siccome non posso garantire l'ordine delle colonne, e per motivi di leggibilità, usare il nome della colonna ha più senso, ma poi devo analizzare personalmente l'output.

Quindi il mio codice sta facendo qualcosa di simile:

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

ho potuto deliberatamente chiamare Enum.ConvertOrDefault invece di Convert quando mi aspetto una Enum, ma che sembra rompere il principio di un metodo generale, che credo ha più senso ... e mi permetterebbe anche di riutilizzare quel metodo in altri contesti.

La speranza che aiuta a chiarire un po '.

--- --- Modifica 3

ho provato questa idea, dai commenti:

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

e

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 

Ma questo dà errori quando chiamo il Metodo Convert per tipi come String o Byte(), dicendo

"Tipo di argomento 'String' deve avere un'istanza senza parametri pubblica costruttore per soddisfare il vincolo 'Nuovo' per il parametro di tipo 'T'

+0

Un modo leggermente disordinato sarebbe aggiungere 'Enum' ai vincoli, ma poi forzare un errore nell'istanza. Questo fa qualcosa di molto simile a un fallimento di vincoli. – Paul

+0

Non sono sicuro di aver capito, mi dispiace. Aggiungi 'Enum' dove? – simonalexander2005

+0

Semplicemente ignorami - dimentica che non puoi aggiungere Enum ai vincoli ...: -/ – Paul

risposta

1

Che funziona: D Grazie! Se scrivi che come risposta puoi avere la taglia. È un peccato non poter rispondere alla domanda così com'è - "Come posso dire" va bene, so che è un Enum quindi va bene? ", Oppure, puoi limitare ulteriormente T quando lo passi ad un altro metodo.

L'implementazione di ciò che ho consigliato è mostrata di seguito. Come tentativo di spiegare perché non puoi fare ciò che vuoi fare, forse questo ti sarà d'aiuto. Poiché in Convert(Of T) e T possono rappresentare qualsiasi tipo, non è possibile per il compilatore garantire la sicurezza del tipo quando lo si passa a un tipo generico più limitato; c'è la funzione CType per i tipi generici.

Non capisco perché il metodo Enum.TryParse richiama un vincolo di struttura.Guardando il source code controlla ancora la proprietà Type.IsEnum, quindi sembra superfluo provare e imporre un vincolo parziale.

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

Si consiglia di considerare l'utilizzo di un diverso tipo di set enumerato di valori. Puoi invece utilizzare il modello enum polymorphic/class/subclassable.

Quelli che uso di solito hanno un metodo TrySelect per risolvere i valori sottostanti all'enumerazione. Inoltre, è possibile supportare più valori sottostanti di diversi tipi per ciascun valore enum. Per esempio:

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

} 

Quindi è possibile aggiungere metodi alle tue enumerazioni per le operazioni più specializzati. È possibile creare mappe con le proprie enumerazioni che associano la colonna alla posizione dell'indice, ecc. È inoltre possibile eseguire facilmente iterazioni sull'insieme di valori enumerati o valori sottostanti utilizzando semplicemente le proprietà All* (BoolEnum.AllValues, BoolEnum.AllBooleanValues, BoolEnum.AllIntegerValues).

FYI> Non è così difficile da implementare utilizzando i generici in modo che la maggior parte del boilerplate sia ASCIUGATO via. L'esempio subclassable (dichiarazione di non responsabilità: questo è il mio articolo su questo) si basa sull'uso di una classe di enum di base generica.

Ecco un dotnetfiddle che mostra quanto sopra esempio enum in azione: https://dotnetfiddle.net/O5YY47

Ecco una versione VB.Net di quanto sopra:

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 

E qui è la dotnetfiddle per la versione VB.Net : https://dotnetfiddle.net/HeCA5r

1

Si sta utilizzando VB.NET, una lingua che è già abbastanza amichevole per la digitazione dinamica. C'è poco che puoi fare con i vincoli di tipo generico su Enums, una limitazione piuttosto difficile in .NET. Il problema principale è che i tipi di enumerazione non possono comportarsi in modo generico, la loro dimensione di archiviazione dipende dal tipo specifico. Quale può essere 1, 2, 4 o 8 byte, a seconda del tipo di base. Questo è un grosso problema per i generici, il cookie-cutter (aka MSIL) è diverso.

Quindi, basta punt sul problema, VB.NET fornisce la palla, in un caso come questo ti piace molto la funzione di supporto Conversion.CTypeDynamic(). Hai solo bisogno di un po 'di codice in più per gestire oggetti null e distinzione tra maiuscole e minuscole. Si dovrebbe anche prendere in considerazione la gestione DBNull quando si esegue questa operazione per le conversioni di campo dBASE:

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 

Nota l'altro dettaglio di implementazione VB.NET in questo codice, non è necessario new T. Nulla è già un valore perfetto per un enum. E non è necessario lanciare eccezioni, CTypeDynamic si lamenta già con un messaggio di eccezione documentato che è localizzato.