2012-01-13 15 views
7

In breve: è possibile definire un metodo generico in cui il parametro type (T) è vincolato a string o int[]? In pseudo-C#, quello che voglio fare è:È possibile vincolare un parametro di tipo generico a String OR Array

public static int MyMethod<T> (T arg1, T arg2) 
    where T : (has an indexer that returns an int) { 
    // stuff with arg1[i], arg2[j], etc... 
} 

Si noti che in C#, a causa del built-in string indicizzatore (che restituisce una char) con conversione implicita da char al int, la seguente espressione significa esattamente la stessa cosa se source è un string o un int[]:

int someval = source[index]; 

per il filo Constrain generic extension method to base types and string mi rendo conto che non posso solo fare un elenco dei tipi non associati nel where T : x... clausola di vincolo. Entrambi int[] e string sono conformi a T: IEnumerable<int>, ma IEnumerable<T> non richiede agli implementatori di avere un indicizzatore, che è esattamente la caratteristica comune che sto utilizzando da entrambi i tipi.

Lo scopo di questo è che sto creando alcune funzioni di analisi e analisi delle stringhe altamente ottimizzate, come una rapida implementazione dell'algoritmo di distanza Damerau-Levenshtein. Ho scoperto che per prima cosa la conversione delle stringhe in matrici di int può talvolta produrre esecuzioni significativamente più veloci nell'elaborazione ripetitiva di carattere per carattere (come con l'algoritmo D-L). Ciò è in gran parte dovuto al fatto che confrontare i valori int è molto più veloce rispetto ai valori char.

La parola chiave è "a volte". A volte è più veloce operare direttamente sulle stringhe ed evitare il costo della prima conversione e copia negli array di int. Quindi ora ho metodi che sono veramente identici tranne che per le dichiarazioni.

Naturalmente posso usare dynamic, ma la penalità delle prestazioni dal controllo in fase di esecuzione distrugge completamente i guadagni realizzati nella costruzione dei metodi. (I ha effettuato il test).

+0

In che senso è sensato trattare 'T' staticamente * * * un tipo o un altro? Il punto di vincoli è quello di consentire di operare su variabili di tipo "T" e prevedere un livello minimo di compatibilità del tipo. Vincolare a due tipi incompatibili tra loro è bizzarro. –

+0

@ kirk-woll: Come descrivo in dettaglio, non sono reciprocamente incompatibili per i miei scopi. Al contrario, sono identici. Ho costruito e sto usando metodi che sono identici al 100%, tranne che i parametri in una versione sono dichiarati come 'stringa' e nell'altra versione sono dichiarati come' int [] '. In entrambi i casi, sto operando su ciascun membro della raccolta come un numero intero. –

+1

No. Una 'stringa' e (qualsiasi)' matrice' unifica a 'oggetto', una stringa * non * un' carattere [] 'in C#, sebbene sia possibile creare un' char [] '* da * una stringa , ma questo è un * nuovo * oggetto array con i contenuti copiati. –

risposta

4

Non si può avere un vincolo che dice "il tipo deve avere un indicizzatore".

Tuttavia, è possibile avere un vincolo che dice "il tipo deve implementare un'interfaccia con un indicizzatore". Tale interfaccia potrebbe essere IList<char>, ad esempio.

Purtroppo, string non implementa IList<char>, in modo che avrebbe dovuto scrivere una piccola classe wrapper per esso:

sealed class StringWrapper : IList<char> 
{ 
    public string String { get; private set; } 
    public StringWrapper(string str) { String = str; } 
    public static implicit operator StringWrapper(string str) 
    { 
     return new StringWrapper(str); 
    } 

    public char this[int index] 
    { 
     get { return String[index]; } 
     set { throw new NotSupportedException(); } 
    } 

    // Etc.... need to implement all the IList<char> methods 
    // (just throw NotSupportedException except in the ones that are trivial) 
} 

E poi si può dichiarare il vostro metodo come questo:

public static TElement MyMethod<TCollection, TElement>(TCollection arg) 
    where TCollection : IList<TElement> 
{ 
    return arg[0]; 
} 

[...] 

MyMethod<StringWrapper, char>("abc")  // returns 'a' 
MyMethod<int[], int>(new[] { 1, 2, 3 }) // returns 1 
+0

Questo in effetti ha funzionato. Un uso molto intelligente della dichiarazione "operatore implicito". Ho modificato l'esempio di 'StringWrapper' per implementare' IList ', vincolato i miei metodi a' T: IList 'e chiamando i metodi con gli argomenti' string' e 'int []' compilati con successo. Sfortunatamente era ancora molto lento, quindi in realtà non lo userò. (Sia le versioni di 'dynamic' che quelle di' IList' presentate erano più lente di 2,8 volte rispetto agli overload espliciti 'int []' e 'string'). –

+0

Sono molto sorpreso che tu dica che è lento come "dinamico", ma tu sembri averlo testato, quindi ti credo. Spero che questo significhi che la dinamica sia sorprendentemente veloce, e non che la distribuzione del metodo di interfaccia sia sorprendentemente lenta. – Timwi

+0

Oggi ho eseguito alcuni test diversi su un insieme più ampio di dati più eterogenei ei miei risultati sono tornati molto diversi: il generico era solo il 5% più lento di un sovraccarico esplicito 'int []'; generico era solo il 15% più lento di un sovraccarico 'stringa' esplicito; mentre 'dynamic' era circa il 50% più lento dei sovraccarichi espliciti. –

5

No, C# non consente di creare un vincolo "composito" come quello.

L'espressione mostrata indica la stessa cosa, ma non è possibile utilizzarla a proprio vantaggio. I generici C# assomigliano sinteticamente ai modelli C++, ma funzionano in modo completamente diverso, quindi il fatto che l'indicizzatore faccia la stessa cosa finisce per essere irrilevante.

Si potrebbe, ovviamente, solo creare i due sovraccarichi con i tipi specifici necessari, ma ciò significa una copia fastidiosa copia &. Qualsiasi tentativo di astrazione di questo per evitare la ripetizione ti costerà molto male le prestazioni.

0

Si potrebbe rendere il vostro metodo di firma

public static int MyMethod<T> (T[] arg1, T[] arg2) 

e utilizzare String.ToCharArray() prima di passare argomenti stringa (o forse in un sovraccarico, si ottiene l'idea ...)

+0

L'unico problema è che l'OP sta "costruendo alcune funzioni di analisi e analisi delle stringhe altamente ottimizzate" - questo non sarebbe affatto veloce. –

+0

Purtroppo, ciò vanifica lo scopo di utilizzare l'indicizzatore di stringhe: accesso all'indice _ senza copiare i contenuti. Se ho intenzione di convertire in un array, mi limiterò a convertire in un array di 'int', fatto facilmente con un metodo di estensione. –

Problemi correlati