2012-02-23 4 views
11

Je sais que ce que je fais peut être fait d'une manière différente, mais je suis curieux de savoir comment les choses fonctionnent. Ce qui suit est un code simplifié qui ne compile pas, mais il est censé montrer mon objectif.Passer une fonction générique comme paramètre

private void Execute() 
{ 
    GeneralizedFunction("1", "2", i => Transform(i)); 
} 

void GeneralizedFunction(string aStringA, string aStringB, Func<string, T> aAction) 
{ 
    A result1 = aAction(aStringA); 
    B result2 = aAction(aStringB); 
    // Do something with A and B here 
} 

T Transform<T>(string aString) 
{ 
    return default(T); 
} 

Transform est un générique de convertion chaîne à un objet (pensez désérialisation). GeneralizedFunction utilise deux spécialisations de transformation: une pour le type A et une pour le type B. Je sais que je peux le faire de plusieurs autres manières (disons en introduisant un paramètre pour le type de l'objet), mais je cherche des explications sur la possibilité ou l'impossibilité de le faire avec des génériques/lambdas. Si Transform est spécialisé avant de passer en paramètre à GeneralizedFunction, alors c'est impossible. Alors la question est pourquoi cette possibilité est limitée.

+0

Qu'est-ce tu veux faire exactement? puisque vous ne voulez pas donner à GeneralizedFunction aucune information de type concernant la fonction Transform, pourquoi ne pas accepter une nouvelle fonction en prenant une chaîne et en retournant un objet (dont tout le monde sait que tout le monde est *) – Polity

+0

A et B "placeholder cache la partie problématique. A et B vont-ils toujours être des types particuliers? Alors vous n'avez pas besoin de génériques. Sont-ils arbitraires (peut-être avec des contraintes)? Ensuite, GeneralizedFunction doit être générique en eux. – AakashM

+0

A et B sont des types concrets, mais Transform est une fonction générique. – Max

Répondre

1

Essayez la signature suivante:

void GeneralizedFunction<T>(string aStringA, string aStringB, Func<string, T> aAction) 

(Notez que GeneralizedFunction doit être générique, le compilateur suppose automatiquement le paramètre de type lors de l'appel de la méthode).

+0

Je l'ai essayé. Le problème est que vous essayez de vous référer aux deux types A et B avec T ici. J'avais l'intention d'enlever de la déclaration de fonction. – Max

+0

Ensuite, vous devrez remplacer à la fois A et B avec T. – Matthias

+0

J'ai besoin d'effectuer des opérations spécifiques sur les objets à l'intérieur de GeneralizedFunction pour plus de simplicité. Je n'ai pas écrit de détails dans mon exemple de code, mais tout ce qui est écrit est nécessaire. – Max

0
void GeneralizedFunction<T>(string aStringA, string aStringB, Func<string, T> aAction) 
{ 
    A result1 = aAction(aStringA); 
    B result2 = aAction(aStringB); 
} 

T Transform<T>(string aString) 
{ 
    return default(T); 
} 
4

Ce que vous demandez de faire n'est pas possible en utilisant uniquement les génériques. Le compilateur doit générer deux versions typées de votre fonction Transform: une pour renvoyer le type A et une pour le type B. Le compilateur n'a aucun moyen de savoir générer ceci au moment de la compilation; C'est seulement en exécutant le code que l'on sait que A et B sont requis.

Une façon de le résoudre serait de passer dans les deux versions:

private void Execute() 
{ 
    GeneralizedFunction("1", "2", i => Transform<A>(i), i => Transform<B>(i)); 
} 

void GeneralizedFunction(string aStringA, string aStringB, Func<string, A> aAction, Func<string, B> bAction) 
{ 
    A result1 = aAction(aStringA); 
    B result2 = bAction(aStringB); 
} 

Le compilateur sait exactement ce dont il a besoin pour générer dans ce cas.

+0

Oui, je sais que je pourrais passer deux instances, mais j'espérais qu'il est possible de passer une fonction GENERIC (d'où le titre de ma question) et de créer deux spécialisations de cette fonction générique à l'intérieur de la fonction GeneralizedFunction. – Max

+0

Je sais que le code n'est pas ce que vous voulez. Votre question était pourquoi la possibilité est limitée. J'espère que vous comprenez maintenant pourquoi ce que vous voulez faire ne fonctionnera pas avec le compilateur. –

+0

Si je regarde le code IL généré pour la fonction Transform, il semble qu'il n'y en ait qu'une seule version. Même quand je l'applique à deux classes d'objets. Il semble donc que la spécialisation d'une fonction générique se fasse au moment de l'exécution. N'est-ce pas? – Max

1

Il semble que la réponse soit "non".

Lorsque vous appelez Transform directement, vous devez spécifier un paramètre de type:

int i = Transform<int>(""); 

donc hypothétiquement, si vous pouviez passer une incomplètement construit fonction générique comme vous voulez, vous aurez besoin de préciser la paramètres de type ainsi:

void GeneralizedFunction(string aStringA, string aStringB, Func<string, T> aAction) 
{ 
    A result1 = aAction<A>(aStringA); 
    B result2 = aAction<B>(aStringB); 
    // Do something with A and B here 
} 

il me semble que vous pourriez hypothétiquement faire, si C# a une syntaxe comme ça.

Mais quel est le cas d'utilisation? Mis à part transformer les chaînes à la valeur par défaut d'un type arbitraire, je ne vois pas beaucoup d'utilité pour cela. Comment pourriez-vous définir une fonction qui fournirait un résultat significatif dans l'un ou l'autre de deux types différents en utilisant la même série d'instructions?

EDIT

Une analyse des raisons pour lesquelles il est impossible:

Lorsque vous utilisez une expression lambda dans votre code, il est compilé dans soit un délégué ou un arbre d'expression; dans ce cas, c'est un délégué. Vous ne pouvez pas avoir une instance d'un type générique "ouvert"; en d'autres termes, pour créer un objet à partir d'un type générique, tous les paramètres de type doivent être spécifiés.En d'autres termes, il n'y a aucun moyen d'avoir une instance d'un délégué sans fournir d'arguments pour tous ses paramètres de type. L'une des fonctionnalités utiles du compilateur C# est la conversion implicite de groupes de méthodes, où le nom d'une méthode (un "groupe de méthodes") peut être implicitement converti en un type délégué représentant l'une des surcharges de cette méthode. De même, le compilateur convertit implicitement une expression lambda en un type délégué. Dans les deux cas, le compilateur émet du code pour créer une instance du type délégué (dans ce cas, pour le passer à la fonction). Mais l'instance de ce type de délégué doit toujours avoir un argument type pour chacun de ses paramètres de type.

Pour passer la fonction générique en fonction générique, il semble, le compilateur devrait être en mesure de passer le groupe de méthode ou l'expression lambda la méthode sans conversion, de sorte que le aAction paramètre aurait en quelque sorte un type de "groupe de méthodes" ou "expression lambda". Ensuite, la conversion implicite en un type délégué peut se produire sur les sites d'appel A result1 = aAction<A>(aStringA); et B result2 = aAction<B>(aStringB);. Bien sûr, à ce stade, nous sommes bien dans l'univers des contrefactuels et des hypothétiques.

La solution que je suis venu avec au cours du déjeuner était ce, en supposant une fonction Deserialize<T> qui prend une chaîne contenant des données sérialisés et retourne un objet de type T:

void GeneralizedFunction<T>(string aStringA, string aStringB, Func<T, string> stringGetter) 
{ 
    A result1 = Deserialize<A>(stringGetter(aStringA)); 
    B result2 = Deserialize<B>(stringGetter(aStringB)); 
} 

void Example(string serializedA, string serializedB, string pathToA, string pathToB, FileInfo a, FileInfo b) 
{ 
    GeneralizedFunction(serializedA, serializedB, s => s); 
    GeneralizedFunction(pathToA, pathToB, File.ReadAllText); 
    GeneralizedFunction(a, b, fi => File.ReadAllText(fi.FullName)); 
} 
+0

Un cas d'utilisation est la désérialisation. La chaîne est une représentation d'un objet et Transform crée une instance de cet objet à partir de sa représentation sous forme de chaîne – Max

+0

@Max Mais quel est le cas d'utilisation pour passer 'Transform' à' GeneralizedFunction' plutôt que de simplement l'appeler directement? Quoi qu'il en soit, un 'T Transform (string) générique 'ne serait-il qu'une méthode pratique autour de' object Deserialize (type, string) 'comme' T Transforme (chaîne s) {return (T) Deserialize (typeof (T) , s); } ' – phoog

+0

Par exemple, Transform1 pourrait convertir une chaîne en un objet, Transform2 pourrait convertir un fichier vers lequel pointe une chaîne – Max

4

Cette réponse n'explique pas la raison pourquoi, juste comment pour contourner la limitation.

Au lieu de passer une fonction réelle, vous pouvez passer un objet qui a une telle fonction:

interface IGenericFunc 
{ 
    TResult Call<TArg,TResult>(TArg arg); 
} 

// ... in some class: 

void Test(IGenericFunc genericFunc) 
{ 
    // for example's sake only: 
    int x = genericFunc.Call<String, int>("string"); 
    object y = genericFunc.Call<double, object>(2.3); 
} 

Pour votre cas d'utilisation spécifique, il peut être simplifié à:

interface IDeserializerFunc 
{ 
    T Call<T>(string arg); 
} 

// ... in some class: 
void Test(IDeserializerFunc deserializer) 
{ 
    int x = deserializer.Call<int>("3"); 
    double y = deserializer.Call<double>("3.2"); 
} 
Questions connexes