2010-05-24 8 views
11

En C# J'essaie d'écrire du code où je créerais un délégué Func qui est en soi générique. Par exemple, le suivant délégué (non générique) retourne une chaîne arbitraire:C# génériques génériques (une question sérieuse)

Func<string> getString =() => "Hello!"; 

I d'autre part vouloir créer un générique qui agit de manière similaire aux méthodes génériques. Par exemple, si je veux un Func générique pour revenir par défaut (T) pour un T. type I imagine que j'écrire du code comme suit:

Func<T><T> getDefaultObject = <T>() => default(T); 

alors je l'utiliser comme

getDefaultObject<string>() qui renverrait null et si je devais écrire getDefaultObject<int>() retournerait 0.

Cette question est non seulement un excercise académique. J'ai trouvé de nombreux endroits où j'aurais pu utiliser ceci mais je ne peux pas obtenir la syntaxe correcte. Est-ce possible? Y a-t-il des bibliothèques qui fournissent ce genre de fonctionnalité?

+5

Y at-il une raison pour laquelle 'Func CreateGetDefaultObject () {return() => par défaut (T); } 'ne fonctionnera pas? –

+0

Je vois maintenant. Vous souhaitez passer un argument de type compilation à un délégué. –

Répondre

4

Bien qu'on puisse trouver pratique solutions de contournement comme Stephen Cleary de

Func<T> CreateGetDefaultObject<T>() { return() => default(T); } 

où vous pouvez spécifier les génériques directement, c'est un problème très intéressant d'un point théorique qui ne peut être résolu par type actuel de C# système.


Un type qui, comme vous l'appelez, est en soi générique, est appelé un type de rang supérieur.

Prenons l'exemple suivant (pseudo-C#):

Tuple<int[], string[]> Test(Func<?> f) { 
    return (f(1), f("Hello")); 
} 

Dans votre système proposé, un appel pourrait ressembler à ça:

Test(x => new[] { x }); // Returns ({ 1 }, { "Hello" }) 

Mais la question est la suivante: Comment pouvons-nous tapons fonction Test et son argument f? Apparemment, f mappe tous les types T à un tableau T[] de ce type. Alors peut-être?

Tuple<int[], string[]> Test<T>(Func<T, T[]> f) { 
    return (f(1), f("Hello")); 
} 

Mais cela ne fonctionne pas. Nous ne pouvons pas paramétrer Test avec un particulierT, puisque f devrait être appliqué à tous types T. À ce stade, le système de type C# ne peut pas aller plus loin.

Ce que nous avions besoin d'une notation comme

Tuple<int[], string[]> Test(forall T : Func<T, T[]> f) { 
    return (f(1), f("Hello")); 
} 

Dans votre cas, vous pouvez taper

forall T : Func<T> getDefaultValue = ... 

La seule langue que je sais qui prend en charge ce genre de médicaments génériques est Haskell:

test :: (forall t . t -> [t]) -> ([Int], [String]) 
test f = (f 1, f "hello") 

Voir cette entrée Haskellwiki sur polymorphism à propos de cette notation forall.

+0

Merci Dario, réponse très intéressante. Bien que je ne sois pas un maître Haskell, j'ai compris votre réponse et le lien que vous avez donné. Si je comprends bien, il n'y a pas de solution/solution de contournement car il s'agit d'une limitation du langage car il ne prend pas en charge les types de rang N. Peut-être que d'ici 5 à 10 ans Microsoft ajoutera "Rank n Types" au CLR. –

+0

@tahirhassan: Oui, c'est une propriété assez fondamentale du système de type de la langue (et une très avancée - même Haskell ne le supporte que par des extensions non standard) qu'on ne peut pas distancer. Une possibilité que je vois bien que vous pouvez trier-de émuler ce comportement en mettant en œuvre une interface comme 'interface ToRankNArray {T [] Appliquer (T arg); } '. Des langages comme Java ou F # permettent la création de sous-types anonymes, mais bien sûr, ce n'est pas aussi pratique qu'une fonction anonyme et cela ne change pas non plus le type-système lui-même. – Dario

-1

Vous ne pouvez pas faire cela, car les paramètres de type générique doivent être connus lors de l'exécution. Vous devez utiliser la classe activateur:

Object o = Activator.CreateInstance(typeof(StringBuilder));

qui fera exactement ce que vous voulez. Vous pouvez l'écrire comme suit:

public T Default<T>() 
{ 
    return (T)Activator.CreateInstance(typeof(T)); 
} 

Modifier

solution de Blindy est mieux.

8

Eh bien, vous ne pouvez pas surcharger quoi que ce soit basée uniquement sur la valeur de retour, donc cela inclut des variables.

Vous pouvez cependant vous débarrasser de cette expression lambda et écrire une fonction réelle:

T getDefaultObject<T>() { return default(T); } 

puis vous l'appeler exactement comme vous voulez:

int i=getDefaultObject<int>();  // i=0 
string s=getDefaultObject<string>(); // s=null 
+0

Merci pour la réponse Blindy, mais la question était plus sur la mise en place dynamique d'un délégué générique générique (si cela est possible). Le problème avec votre code est que vous créez une méthode plutôt qu'un délégué qui peut être déclaré, instancié et utilisé dans une méthode. –

+0

Vrai, mais ne répond pas à la question. – Dario

0

Ce n'est pas possible, puisque un délégué instance en C# ne peut pas avoir de paramètres génériques. Le plus proche que vous pouvez obtenir est de passer l'objet type comme un paramètre normal et d'utiliser la réflexion. :(

Dans de nombreux cas, l'association à dynamique aide à supprimer la douleur de la réflexion, mais dynamique ne vous aide pas lors de la création de nouvelles instances, comme votre exemple.