Generici in C# e accesso ai membri statici di T


12

La mia domanda riguarda C# e come accedere ai meme statici ... Beh, non so davvero come spiegarlo (il che è un problema per una domanda, non è vero? ?) mi limito a darvi alcuni esempi di codice:

Class test<T>{ 
    int method1(Obj Parameter1){ 
     //in here I want to do something which I would explain as 
     T.TryParse(Parameter1); 

     //my problem is that it does not work ... I get an error. 
     //just to explain: if I declare test<int> (with type Integer) 
     //I want my sample code to call int.TryParse(). If it were String 
     //it should have been String.TryParse() 
    } 
} 

quindi grazie ragazzi per le vostre risposte (a proposito la domanda è: come faccio a risolvere questo problema senza ottenere un errore). Questa probabilmente è una domanda facile per te!

Grazie, Niklas


Edit: Grazie a tutti per le vostre risposte!

Anche se penso che la frase try-catch sia la più elegante, so dalla mia esperienza con VB che può davvero essere un disastro. L'ho usato una volta e ci sono voluti circa 30 minuti per eseguire un programma, che in seguito ci sono voluti solo 2 minuti per calcolare solo perché ho evitato il try-catch.

Questo è il motivo per cui ho scelto l'istruzione swich come la migliore risposta. Rende il codice più complicato ma d'altra parte immagino che sia relativamente veloce e relativamente facile da leggere. (Anche se io continuo a pensare che ci dovrebbe essere un modo più elegante ... forse nella lingua prossimo Imparo: P)


Anche se avete qualche altro suggerimento sto ancora aspettando (e disposti a partecipare)

2

Un altro modo per farlo, questa volta un po 'di riflessione nel mix:

static class Parser 
{ 
    public static bool TryParse<TType>(string str, out TType x) 
    { 
     // Get the type on that TryParse shall be called 
     Type objType = typeof(TType); 

     // Enumerate the methods of TType 
     foreach(MethodInfo mi in objType.GetMethods()) 
     { 
      if(mi.Name == "TryParse") 
      { 
       // We found a TryParse method, check for the 2-parameter-signature 
       ParameterInfo[] pi = mi.GetParameters(); 
       if(pi.Length == 2) // Find TryParse(String, TType) 
       { 
        // Build a parameter list for the call 
        object[] paramList = new object[2] { str, default(TType) }; 

        // Invoke the static method 
        object ret = objType.InvokeMember("TryParse", BindingFlags.InvokeMethod, null, null, paramList); 

        // Get the output value from the parameter list 
        x = (TType)paramList[1]; 
        return (bool)ret; 
       } 
      } 
     } 

     // Maybe we should throw an exception here, because we were unable to find the TryParse 
     // method; this is not just a unable-to-parse error. 

     x = default(TType); 
     return false; 
    } 
} 

Il passo successivo sarebbe cercare di implementare

public static TRet CallStaticMethod<TRet>(object obj, string methodName, params object[] args); 

Con la piena tipo di parametro di corrispondenza, ecc

  0

Questo è un po 'lento. Dovresti memorizzare nella cache un delegato generico. 03 gen. 112011-01-03 16:13:24


5

Il problema è che TryParse non è definito su un'interfaccia o una classe base da nessuna parte, quindi non è possibile assumere che il tipo passato alla classe abbia quella funzione. A meno che tu non riesca a contrastare T in qualche modo, ti imbatterai in questo molto.

Constraints on Type Parameters


0

Probabilmente non posso farlo.

Prima di tutto, se dovrebbe essere possibile, è necessario un vincolo più stretto su T, quindi il tipografo potrebbe essere sicuro che tutte le possibili sostituzioni per T in realtà avevano un metodo statico chiamato TryParse.


-1

Non è così che funziona la statica. Devi pensare alla statistica come una sorta di classe Globale, anche se sono distribuiti su un intero gruppo di tipi. Il mio consiglio è di renderlo una proprietà all'interno dell'istanza T che può accedere al metodo statico necessario.

Anche T è un'istanza reale di qualcosa e, proprio come qualsiasi altra istanza, non è possibile accedere alle statistiche per quel tipo, attraverso il valore istanziato. Ecco un esempio di cosa fare:

class a { 
    static StaticMethod1() 
    virtual Method1() 
} 

class b : a { 
    override Method1() return StaticMethod1() 
} 

class c : a { 
    override Method1() return "XYZ" 
} 

class generic<T> 
    where T : a { 
    void DoSomething() T.Method1() 
} 

3

Per accedere a un membro di una classe o di un'interfaccia specifica è necessario utilizzare la parola chiave Dove e specificare la classe di interfaccia o di base che ha il metodo.

Nell'esempio precedente TryParse non proviene da un'interfaccia o da una classe di base, quindi ciò che si sta tentando di fare sopra non è possibile. Meglio usare solo Convert.ChangeType e una dichiarazione try/catch.

class test<T> 
{ 
    T Method(object P) 
    { 
     try { 
      return (T)Convert.ChangeType(P, typeof(T)); 
     } catch(Exception e) { 
      return null; 
     } 
    } 
} 

0

Si consiglia di leggere il mio post precedente su limiting generic types to primitives.Questo può darti alcuni suggerimenti per limitare il tipo che può essere passato al generico (dal TypeParse è ovviamente disponibile solo per un numero impostato di primitive (string.TryParse ovviamente l'eccezione, il che non ha senso) .

una volta che avete più di una maniglia sul tipo, si può quindi lavorare per cercare di analizzarlo. potrebbe essere necessario un po 'di un interruttore di brutto in là (per chiamare il corretto TryParse), ma penso che tu può raggiungere la funzionalità desiderata

Se è necessario che spieghi ulteriormente quanto sopra, chiedi :)


1

hai intenzione di fare qualcosa di simile:

Class test<T> 
{ 
    T method1(object Parameter1){ 

     if(Parameter1 is T) 
     { 
       T value = (T) Parameter1; 
      //do something with value 
      return value; 
     } 
     else 
     { 
      //Parameter1 is not a T 
      return default(T); //or throw exception 
     } 
    } 
} 

Purtroppo non è possibile verificare il modello TryParse come è statica - il che significa, purtroppo, che non è particolarmente adatto per i farmaci generici.


1

L'unico modo per fare esattamente quello che stai cercando potrebbe essere quella di utilizzare la riflessione per verificare se il metodo esiste per T.

Un'altra opzione è quella di garantire che l'oggetto che si invia in è un oggetto convertibile da trattenendo il tipo in IConvertible (tutti i tipi primitivi implementano IConvertible). Questo ti permetterebbe di convertire il tuo parametro in un dato tipo in modo molto flessibile.

Class test<T> 
{ 
    int method1(IConvertible Parameter1){ 

     IFormatProvider provider = System.Globalization.CultureInfo.CurrentCulture.GetFormat(typeof(T)); 

     T temp = Parameter1.ToType(typeof(T), provider); 
    } 
} 

Si potrebbe anche fare una variazione su questo utilizzando un tipo di 'oggetto' invece come si era originariamente.

Class test<T> 
{ 
    int method1(object Parameter1){ 

     if(Parameter1 is IConvertible) { 

      IFormatProvider provider = System.Globalization.CultureInfo.CurrentCulture.GetFormat(typeof(T)); 

      T temp = Parameter1.ToType(typeof(T), provider); 

     } else { 
      // Do something else 
     } 
    } 
} 

3

Risposta breve, non è possibile.

risposta lunga, si può barare:

public class Example 
{ 
    internal static class Support 
    { 
     private delegate bool GenericParser<T>(string s, out T o); 
     private static Dictionary<Type, object> parsers = 
      MakeStandardParsers(); 
     private static Dictionary<Type, object> MakeStandardParsers() 
     { 
      Dictionary<Type, object> d = new Dictionary<Type, object>(); 
      // You need to add an entry for every type you want to cope with. 
      d[typeof(int)] = new GenericParser<int>(int.TryParse); 
      d[typeof(long)] = new GenericParser<long>(long.TryParse); 
      d[typeof(float)] = new GenericParser<float>(float.TryParse); 
      return d; 
     } 
     public static bool TryParse<T>(string s, out T result) 
     { 
      return ((GenericParser<T>)parsers[typeof(T)])(s, out result); 
     } 
    } 
    public class Test<T> 
    { 
     public static T method1(string s) 
     { 
      T value; 
      bool success = Support.TryParse(s, out value); 
      return value; 
     } 
    } 
    public static void Main() 
    { 
     Console.WriteLine(Test<int>.method1("23")); 
     Console.WriteLine(Test<float>.method1("23.4")); 
     Console.WriteLine(Test<long>.method1("99999999999999")); 
     Console.ReadLine(); 
    } 
} 

ho fatto un dizionario statica in possesso di un delegato per il metodo TryParse di ogni tipo potrei voler utilizzare. Ho quindi scritto un metodo generico per cercare il dizionario e passare la chiamata al delegato appropriato. Dal momento che ogni delegato ha un tipo diverso, li memorizzo come riferimenti a oggetti e li restituisco al tipo generico appropriato quando li recupero. Si noti che, per un semplice esempio, ho omesso il controllo degli errori, ad esempio per verificare se abbiamo una voce nel dizionario per il tipo specificato.


2

Ok ragazzi: Grazie per tutto il pesce. Ora con le tue risposte e la mia ricerca (in particolare l'articolo su limiting generic types to primitives) ti presenterò la mia soluzione.

Class a<T>{ 
    private void checkWetherTypeIsOK() 
    { 
     if (T is int || T is float //|| ... any other types you want to be allowed){ 
      return true; 
     } 
     else { 
      throw new exception(); 
     } 
    } 
    public static a(){ 
     ccheckWetherTypeIsOK(); 
    } 
} 

0

Codice migliore: limitare T a ValueType in questo modo:

class test1<T> where T: struct 

Una "struttura" indica un tipo di valore. La stringa è una classe, non un tipo di valore. int, float, Enum sono tutti tipi di valore.

btw il compilatore non accetta chiamare metodi statici o accedere membri statici sui 'parametri di tipo' come nel seguente esempio che non si compila :(

class MyStatic { public static int MyValue=0; } 
class Test<T> where T: MyStatic 
{ 
    public void TheTest() { T.MyValue++; } 
} 

=> errore 1 'T' è un 'parametro type', che non è valido nel contesto dato

SL


1

Questo non è davvero una soluzione, ma in alcuni scenari potrebbe essere una buona alternativa. Siamo in grado di passare un delegato supplementare per il metodo generico

Per chiarire cosa intendo, facciamo un esempio. Diciamo che abbiamo un metodo factory generico, che dovrebbe creare un'istanza di T, e vogliamo che chiamino un altro metodo, per la notifica o l'inizializzazione aggiuntiva.

Si consideri il seguente semplice classe:

public class Example 
{ 
    // ... 

    public static void PostInitCallback(Example example) 
    { 
     // Do something with the object... 
    } 
} 

E il seguente metodo statico:

public static T CreateAndInit<T>() where T : new() 
{ 
    var t = new T(); 
    // Some initialization code... 
    return t; 
} 

Così adesso ci sarebbe da fare:

var example = CreateAndInit<Example>(); 
Example.PostInitCallback(example); 

Tuttavia, abbiamo potuto cambia il nostro metodo per assumere un delegato aggiuntivo:

public delegate void PostInitCallback<T>(T t); 
public static T CreateAndInit<T>(PostInitCallback<T> callback) where T : new() 
{ 
    var t = new T(); 
    // Some initialization code... 
    callback(t); 
    return t; 
} 

E ora siamo in grado di cambiare la chiamata a:

var example = CreateAndInit<Example>(Example.PostInitCallback); 

Ovviamente questo è utile solo in scenari molto specifici. Ma questa è la soluzione più pulita, nel senso che otteniamo la sicurezza del tempo di compilazione, non è coinvolto alcun "hacking" e il codice è semplice.