Inférence de type générique C # 3.0 - passage d'un délégué en tant que paramètre de fonction

StackOverflow https://stackoverflow.com/questions/407983

Question

Je me demande pourquoi le compilateur C # 3.0 est incapable de déduire le type d'une méthode lorsqu'il est passé en tant que paramètre à une fonction générique lorsqu'il peut créer implicitement un délégué pour la même méthode.

Voici un exemple:

class Test
{
    static void foo(int x) { }
    static void bar<T>(Action<T> f) { }

    static void test()
    {
        Action<int> f = foo; // I can do this
        bar(f); // and then do this
        bar(foo); // but this does not work
    }   
}

J'aurais pensé pouvoir transmettre foo à bar et laisser le compilateur déduire le type de Action<T> à partir de la signature de la fonction en cours de transmission, mais cela ne fonctionne pas. Cependant, je peux créer un Action<int> à partir de <=> sans transtypage. Y a-t-il une raison légitime pour que le compilateur ne puisse pas faire la même chose via l'inférence de type?

Était-ce utile?

La solution

Cela clarifiera peut-être les choses:

public class SomeClass
{
    static void foo(int x) { }
    static void foo(string s) { }
    static void bar<T>(Action<T> f){}
    static void barz(Action<int> f) { }
    static void test()
    {
        Action<int> f = foo;
        bar(f);
        barz(foo);
        bar(foo);
        //these help the compiler to know which types to use
        bar<int>(foo);
        bar( (int i) => foo(i));
    }
}

foo n'est pas une action - foo est un groupe de méthodes.

  • Dans l'instruction d'affectation, le compilateur peut indiquer clairement de quel truc vous parlez, car le type int est spécifié.
  • Dans l'instruction barz (foo), le compilateur peut dire de quel foo vous parlez, car le type int est spécifié.
  • Dans la déclaration bar (foo), il peut s'agir de n'importe quel foo avec un seul paramètre - le compilateur abandonne donc.

Éditer: j'ai ajouté deux méthodes (ou plus) pour aider le compilateur à déterminer le type (c.-à-d. comment ignorer les étapes d'inférence).

D'après ma lecture de l'article dans la réponse de JSkeet, la décision de ne pas déduire le type semble reposer sur un scénario d'inférence mutuelle, tel que

  static void foo<T>(T x) { }
  static void bar<T>(Action<T> f) { }
  static void test()
  {
    bar(foo); //wut's T?
  }

Puisque le problème général était insoluble, ils ont choisi de laisser des problèmes spécifiques pour lesquels une solution existe non résolus.

En conséquence de cette décision, vous ne créerez pas de surcharge pour une méthode et ne générerez pas beaucoup de confusion de type de la part de tous les appelants utilisés par un groupe de méthodes membre unique. Je suppose que c'est une bonne chose.

Autres conseils

Le raisonnement est que si le type se développe jamais, il ne devrait y avoir aucune possibilité d’échec. c'est-à-dire que si une méthode foo (chaîne) est ajoutée au type, le code existant ne devrait jamais avoir d'importance - tant que le contenu des méthodes existantes ne change pas.

Pour cette raison, même lorsqu'il n'y a qu'une seule méthode foo, une référence à foo (appelé groupe de méthodes) ne peut pas être transtypée en un délégué non spécifique à un type, tel que Action<T>, mais uniquement en type- délégué spécifique tel que Action<int>.

C'est un peu étrange, oui. La spécification C # 3.0 pour l'inférence de type est difficile à lire et contient des erreurs, mais il semble que cela devrait fonctionner. Dans la première phase (section 7.4.2.1), je pense qu’il ya une erreur: il ne faut pas mentionner les groupes de méthodes dans la première puce (car ils ne sont pas couverts par l’inférence de type de paramètre explicite (7.4.2.7)), ce qui signifie qu’il devrait utiliser inférence de type de sortie (7.4.2.6). Cela semble comme si cela devrait fonctionner - mais évidemment cela ne fonctionne pas: (

Je sais que MS cherche à améliorer les spécifications d'inférence de type, de sorte que cela devienne un peu plus clair. Je sais également que, quelles que soient les difficultés de lecture, des restrictions s’appliquent aux groupes de méthodes et à l’inférence de types, restrictions qui peuvent être spécifiées lorsque le groupe de méthodes n’est en réalité qu’une méthode unique, il est vrai.

Eric Lippert a une entrée de blog sur l'inférence de type de retour ne fonctionne pas avec les groupes de méthodes , ce qui est similaire à ce cas - mais nous ne le sommes pas intéressé par le type de retour, uniquement sur le type de paramètre. Il est possible que d'autres publications de sa série d'inférences de types peut aider si.

Gardez à l’esprit que la mission

Action<int> f = foo;

a déjà beaucoup de sucre syntaxique. Le compilateur génère du code pour cette instruction:

Action<int> f = new Action<int>(foo);

L’appel de méthode correspondant est compilé sans problème:

bar(new Action<int>(foo));

Fwiw, il est donc utile d'aider le compilateur à déduire l'argument de type:

bar<int>(foo);

Cela revient donc à la question suivante: pourquoi le sucre dans la déclaration d'affectation, mais pas dans l'appel de méthode? Je devine que c'est parce que le sucre est sans ambiguïté dans la cession, il n'y a qu'une seule substitution possible. Mais dans le cas d'appels de méthodes, les rédacteurs du compilateur devaient déjà traiter le problème de résolution de surcharge. Les règles sont assez élaborées. Ils ne l’ont probablement pas fait.

Pour compléter, ceci n'est pas spécifique au C #: le même code VB.NET échoue de la même manière:

Imports System

Module Test
  Sub foo(ByVal x As integer)
  End Sub
  Sub bar(Of T)(ByVal f As Action(Of T))
  End Sub

  Sub Main()
    Dim f As Action(Of integer) = AddressOf foo ' I can do this
    bar(f) ' and then do this
    bar(AddressOf foo) ' but this does not work
  End Sub
End Module

erreur BC32050: le paramètre de type 'T' pour 'Barre secondaire publique (De T) (f En tant que System.Action (Of T))' ne peut pas être déduit.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top