2009-03-13 13 views
13

Qualcuno può spiegare questo comportamento in Generics?C# Funzione generica

Ho una funzione generica in C#

protected virtual void LoadFieldDataEditor <T> (ref T control, string strFieldName) where T : Control 
{ 
    //T can be different types of controls inheriting from System.Web.UI.Control 
    if (control is TextBox) 
    { 
    //This line gives an error 
    //((TextBox)control).Text = "test"; 

    //This line works! 
    (control as TextBox).Text = "Test"; 
    } 
} 

Una nota a parte, posso usare il caso interruttore quando sto facendo un "controllo TextBox è" tipo di controllo?

EDIT:

dimenticato di aggiungere il messaggio di errore Sorry!

Qui si va:

Error 3 Cannot convert type 'T' to 'TextBox' 

EDIT:

Mentre stiamo parlando di farmaci generici, ho un'altra domanda. (Non ero sicuro se ho dovuto iniziare un nuovo post)

Il metodo è stato ampliato per includere un altro tipo generico

protected virtual void LoadFieldDataEditor <T1, T2> (T1 control, T2 objData, string strFieldName) where T1 : Control where T2 : BaseDataType 
{ 
    //I will need to access field1. 
    //I don't know at compile time if this would be SomeType1 or 
//SomeType2 but all of them inherit from BaseDataType. 

    //Is this possible using generics? 
} 

public abstract class BaseDataType {} 

public class SomeType1 : BaseDataType 
{ 
    string field1; 
    string field2; 
} 
+0

Quando stai chiedendo cosa significa un errore, è davvero utile dire qual è l'errore. Sto solo cercando di riprodurlo ora ... –

+0

Qual è l'errore? –

+0

@Jon Skeet: Dannazione ... Non mi ero reso conto che fossi sul caso ... –

risposta

21

Le regole per ciò che un tipo generico può essere convertito in sono abbastanza difficile, e occasionalmente controintuitivo, come in questo caso. Vedere la sezione 6.2.6 della specifica C# per i dettagli. Ci sono posti dove potrebbero essere più rilassati, e penso che questo sia uno di questi. È possibile eseguire il cast fino a object e quindi giù di nuovo, ma questo è brutto.

In questo caso la soluzione migliore sarebbe:

protected virtual void LoadFieldDataEditor <T> (ref T control, 
               string strFieldName) 
    where T : Control 
{ 
    TextBox textBox = control as TextBox; 
    if (textBox != null) 
    { 
     textBox.Text = "test"; 
    } 
} 

parte altro, ciò richiede un solo assegno tempo di esecuzione invece di due.

Per la nota a margine: no, non è possibile utilizzare l'interruttore/caso sui tipi. (È possibile ottenere il nome del tipo e accenderlo, ma sarebbe orribile.)

+0

Stai creando un nuovo textBox con questo metodo. Cambiando il testo su questo cambierà il testo su quello che è stato inviato come parametro (ref Control)? – DotnetDude

+0

No, questo metodo non crea un nuovo TextBox. Quale linea pensi crei una nuova? Sospetto fortemente che in realtà non sia necessario passare il controllo per ref ... –

+0

Ho pensato che questa riga - TextBox textBox = control come TextBox; creato una nuova casella di testo. Potrei dover rispolverare il mio "passaggio per val" e passare per concetti di riferimento. La mia comprensione è stata quando si passa per valore (cioè senza ref) crea una nuova var in una nuova posizione di memoria e copia il valore sorgente – DotnetDude

1

La prima riga restituisce l'errore del compilatore: "Impossibile convertire il tipo T in TextBox." Questo tipo di cast è legale solo se il compilatore può sapere che è possibile convertire la classe di partenza nella classe finale. Perché T potrebbe essere qualsiasi cosa, non c'è modo per il compilatore di sapere. Anche se stai controllando in runtime, questo non appaga il compilatore. Il secondo tipo di cast è OK, perché restituirà null se il cast non funziona. EDIT: Come sottolineano tuinstoel, le regole per il casting sono più complicate di quelle che ho descritto.

+0

Vedere la mia risposta. – tuinstoel

0

In risposta a @rossfabricant.

Non è così semplice, il primo metodo si compila, il secondo no.

void Test(Control control) 
{ 
    if (control is TextBox) 
    { 
     ((TextBox)control).Text = "test"; 
    } 
} 

void Test<T>(T control) where T : Control 
{ 
    if (control is TextBox) 
    { 
     ((TextBox)control).Text = "test"; 
    } 
} 
3

Mi raccomando refactoring che questo sia:

protected virtual void LoadFieldDataEditor(Control control, string strFieldName) 

Come accennato in un paio di osservazioni, questo metodo non ha bisogno di farmaci generici a tutti.

Poiché si limita a un controllo, si conosce la classe base e si tratta di un tipo di riferimento (controllo), in modo da evitare i generici e la dichiarazione dei parametri ref.

Poiché Control è un tipo di riferimento, è possibile modificarne le proprietà nel metodo e questo funzionerà correttamente. L'impostazione .Text, ecc. Farà esattamente ciò che stai cercando di fare, ma sarà molto più semplice.

C'è una piccola possibilità che si potrebbe bisogno di essere:

protected virtual void LoadFieldDataEditor(ref Control control, string strFieldName) 

ma questo sarebbe necessaria solo se si andavano a riassegnare il controllo all'interno del vostro metodo (es: control = new TextBox();). Consiglio vivamente di non farlo, in quanto può causare un comportamento molto inaspettato e non sarebbe ovvio. Se si sta tentando di creare un nuovo controllo, l'utilizzo di un parametro out o la semplice restituzione del nuovo controllo renderebbero tutto molto più chiaro.

Inoltre, in generale, è una buona idea evitare metodi generici a meno che non vi sia una buona ragione per includerli. Il team di FxCop ha aggiunto (e successivamente rimosso) alcune regole che cercano di scoraggiare il loro uso perché tendono a rendere il codice meno comprensibile a lungo termine. Ci sono molte buone ragioni per usare metodi generici, ma questo non richiede il loro uso, quindi consiglierei di evitarli.

+0

Grazie per la spiegazione. Assegnare il testo era solo allo scopo di porre la domanda. In realtà, ho un sacco di altre cose in corso. Ma sì, ho fatto il refactoring per non usare ref e funziona alla grande! Grazie – DotnetDude