Codingly

Convertir implicitement des delegates ayant la même signature

Posted in Articles by Romain Verdier on février 10, 2010

Hier soir, je tombe sur un tweet de Krzysztof Koźmic :

Do I care if it’s Predicate<int> or Func<int>? No, than why won’t C# compiler just live me alone and pick whatever it needs? I DONT CARE

Et là je me dis HAHA LOL MAIS N’IMPORTE QUOI Predicate<int> et Func<int> c’est très différent voilà une bonne occasion pour moi qui ne suis rien de reprendre gratuitement un des contributeurs principaux de Castle sur une étourderie. Et je lui fais remarquer, en gros :

Pardon, mais en fait, je crois que Predicate<int> et Func<int> sont des délégués bien différents :)

Et tac ! Bon, évidemment, Krzystzof Kzrysztof Krzysztof pensait bien à Func<int, bool> qui partage effectivement la même signature que Predicate<int>. Son commentaire, pertinent, est donc celui d’un programmeur pestant contre le fait que les délégués, en C#, et au niveau même de la CLI, soient des types nominatifs et non structurels.

Considérons les deux types suivants :

    class Tweet
    {
        public string Message;
    }

    class Buzz
    {
        public string Message;
    }

Nous sommes alors ravis de ne pouvoir écrire :

    Tweet tweet = new Buzz();

Car même si structurellement, les instances de Tweet peuvent être considérées compatibles avec les instances de Buzz, sémantiquement c’est une autre histoire. Les points en coordonnées cartésiennes ne sont pas des points en coordonnées polaires, les chiens ne sont pas des chats, etc.

Dans le CLR, tous les types sont nominatifs, et c’est très bien comme ça. Sauf quand ce n’est pas très bien ; Krzysztof et moi pensons par exemple aux délégués.

Le premier problème est qu’il est impossible de convertir implicitement une instance de delegate en un autre type de delegate structurellement compatible, e.g. : on ne peut pas « convertir » une instance de Func<int, bool> en Predicate<int> :

    Func<int, bool> func = i => false;
     
    // La ligne ci-dessous lève une erreur de compilation :
    // Cannot implicitly convert type 'System.Func<int,bool>' to 'System.Predicate<int>'
    Predicate<int> predicate = func;

Cependant, il existe plusieurs façons de contourner le problème, dont une particulièrement acceptable :

EventHandler eventHandler = (s, e) => {};
Action<object, EventArgs> action;

action = (s,e) => eventHandler(s,e); // OK, mais lourd.
action = new Action<object, EventArgs>(eventHandler); // OK, mais verbeux.
action = eventHandler.Invoke; // OK tout court.

Notons que les deux dernières lignes produisent le même code IL, puisqu’on laisse simplement le compilateur générer automatiquement l’appel au constructeur de Action<object, EventArgs> à partir du method group Invoke compatible. Quels que soient donc les types de délégués structurellement compatibles avec lesquels on joue, la « conversion », certes explicite, ne coutera que 7 caractères. C’est 133 de moins qu’un tweet.

Le second problème, plus gênant selon moi, est qu’il est impossible aujourd’hui de comparer les références de deux instances de délégués structurellement compatibles mais de types différents, alors que selon la sémantique de l’opérateur d’égalité de Delegate, ça ne devrait pas être gênant :

Predicate<string> predicate1 = string.IsNullOrEmpty;
Predicate<string> predicate2 = string.IsNullOrEmpty;
Func<string, bool> func = string.IsNullOrEmpty;

if (predicate1 == predicate2) // Vrai
    Console.WriteLine("predicate1 == predicate2");

if (predicate1.Equals(func)) // Faux
    Console.WriteLine("predicate1 == func");

Note : Une ancienne version de la spécification C# laisse entendre que c’était possible :

Note that delegates of different types can be considered equal by the above definition, as long as they have the same return type and parameter types.

Mais dans la dernière version spécifie explicitement que c’est impossible :

If the delegates have different runtime type they are never equal.

Bref.

Les plus curieux peuvent alors se demander pourquoi Microsoft n’a pas choisit de faire des delegates des types structurels. Lorsqu’on se pose une question de ce genre — et j’en profite, amis lecteurs, pour vous poser une petite devinette — à qui est-il intéressant de la soumettre :

  • Eric Naulleau ?
  • Eric Zemmour ?
  • Eric Lippert ?

Bravo amis lecteurs, c’est effectivement à M. Zemmour que j’ai envoyé un mail. Voici donc un extrait de sa réponse :

In practice, almost everyone uses delegates structurally, and almost no one makes semantic distinctions between different kinds of delegate types. There’s no semantic reason why Predicate and Func couldn’t be used interchangeably; they are interesting solely for their structure. (Though of course it is nice if a predicate is a pure function!)

If we had a chance to do it all over again, I think we would make delegates into structural types. Also, note that the new « no PIA » feature of C# 4 is essentially structural typing on interfaces. We probably erred a little too far on the side of caution when making all types non-structural in CLR v1, and now we’re gradually weakening that. I would not be too surprised if in some future version of the CLR, it was easier to make structural delegate types, but, no promises. That’s just a speculation.

En deux mots, Eric Lippert (oui car en fait c’est Eric Lippert, hein), m’a répondu qu’il serait tenté de faire passer les delegates du côté des types structurels s’il en avait l’occasion, et qu’il ne serait pas forcément étonné si cela arrivait dans une version future du CLR.

Stay tuned !

Tagged with: ,

5 Réponses

Subscribe to comments with RSS.

  1. Jb Evain said, on février 11, 2010 at 3:03

    Ça a le mérite de pouvoir se débattre. Avant l’arrivée des génériques et des délégués de la famille de Func et d’Action, l’avantage des délégués c’était la sémantique attachés à leur nom.

    Imaginons un:

    public delegate string Lolcatize (string s);

    Et un:

    public delegate string Normalize (string s);

    Les deux ont une signature équivalente mais une sémantique entièrement différente. Et je n’ai certainement pas envie qu’ils soient égaux devant la loi de l’opérateur ==.

    Et pourtant je suis le premier à remplacer des délégués de classe par des Func.

    • Romain Verdier said, on février 11, 2010 at 3:30

      Oui, tout à fait, l’implémentation actuelle des delegates permet d’associer des sémantiques différentes à types de délégués ayant des signatures comparables, ce qui peut parfois être désirable. Autre extrait de la réponse Lippertienne :

      Suppose I had:

      delegate R PureFunction<A>(A a);
      delegate R DirtyFunction<A>(A a);

      A « pure function » is a function that has no side effects, never throws an exception, and always returns the same value when given the same argument. Pure functions are nice because you can do interesting things with them. You can cache their results to avoid recomputation. You can defer their execution until the result is needed. And so on. Dirty functions might have side effects, throw exceptions, or return results based on external conditions.

      It should not be legal to assign a value of type DirtyFunction to a variable of type PureFunction because their semantics are different. It’s the same as the thing with two kinds of points; even though the structure is the same, we cannot guarantee that the *meaning* is the same, so we don’t allow it.

      Seulement rien n’empêche quelqu’un d’instancier le delegate à partir d’une fonction ou d’une méthode anonyme qui ne respecte cette sémantique…

      Aussi, je viens de trouver sur cette page la confirmation qu’il y a bien eu un changement entre les premières versions du framework (1.0 et 1.1) et les versions ultérieures au niveau du comportement de l’opérateur d’égalité :

      In the .NET Framework version 1.0 and 1.1, two delegates were considered equal if their targets, methods, and invocation list were equal, even if the delegates were of different types.

  2. Vincent B. said, on février 12, 2010 at 8:59

    Je m’étais déjà posé la question effectivement.
    Mais j’ai jamais chercher …
    Comme dirait JB : « si ils cherchent c’est qu’ils n’ont pas trouvé… »

    Merci Romain, je vais pouvoir passer une bonne nuit maintenant :p

  3. Romain Du 12 said, on mars 26, 2010 at 3:37

    Bonjour Romain, ça faisait longtemps.

    Je prends toujours autant de plaisir à lire ton blog, et même si la plupart du temps je n’y comprend ABSOLUMENT RIEN, tu me fais bien rire quand même.

    Keep going

  4. Clement Verdier said, on avril 26, 2010 at 5:40

    Ton blog a l’air de passionner, même si moi aussi je n’y comprend rien ^^
    Bonne continuation ! c u :)


Laisser un commentaire

Entrez vos coordonnées ci-dessous ou cliquez sur une icône pour vous connecter:

Logo WordPress.com

Vous commentez à l'aide de votre compte WordPress.com. Déconnexion / Changer )

Image Twitter

Vous commentez à l'aide de votre compte Twitter. Déconnexion / Changer )

Photo Facebook

Vous commentez à l'aide de votre compte Facebook. Déconnexion / Changer )

Photo Google+

Vous commentez à l'aide de votre compte Google+. Déconnexion / Changer )

Connexion à %s

Suivre

Recevez les nouvelles publications par mail.

%d blogueurs aiment cette page :