42 votes

Le délégué 'System.Action' ne prend pas 0 argument. S'agit-il d'un bogue du compilateur C# (lambdas + deux projets) ?

Considérez le code ci-dessous. Cela ressemble à du code C# parfaitement valide, non ?

//Project B
using System;
public delegate void ActionSurrogate(Action addEvent);
//public delegate void ActionSurrogate2();
// Using ActionSurrogate2 instead of System.Action results in the same error
// Using a dummy parameter (Action<double, int>) results in the same error

// Project A
public static class Class1 {
    public static void ThisWontCompile() {
        ActionSurrogate b = (a) =>
                            {
                                a(); // Error given here
                            };
    }
}

J'obtiens une erreur de compilation "Delegate 'Action' does not take 0 arguments." à la position indiquée en utilisant le compilateur (Microsoft) C# 4.0. Notez que vous devez déclarer ActionSurrogate dans un autre projet pour que cette erreur se manifeste.

Ça devient plus intéressant :

// Project A, File 1
public static class Class1 {
    public static void ThisWontCompile() {
        ActionSurrogate b = (a) => { a(); /* Error given here */ };
        ActionSurrogate c = (a) => { a(); /* Error given here too */ };
        Action d = () => { };
        ActionSurrogate c = (a) => { a(); /* No error is given here */ };
    }
}

Est-ce que je suis tombé sur un bug du compilateur C# ?

Notez que c'est un bug assez ennuyeux pour quelqu'un qui aime beaucoup utiliser les lambdas et qui essaie de créer une bibliothèque de structures de données pour une utilisation future... (moi)

EDIT : suppression de la casse erronée.

J'ai copié et réduit mon projet original au minimum pour le réaliser. C'est littéralement tout le code de mon nouveau projet.

67voto

Eric Lippert Points 300275

MISE À JOUR FINALE :

Le bogue a été corrigé dans C# 5. Toutes nos excuses pour le désagrément, et merci pour le rapport.


Analyse originale :

Je peux reproduire le problème avec le compilateur en ligne de commande. Cela ressemble certainement à un bug. C'est probablement ma faute ; désolé. (J'ai écrit tout le code de vérification de la conversion lambda-délégué).

Je suis dans un café en ce moment et je n'ai pas accès aux sources du compilateur d'ici. Je vais essayer de trouver un peu de temps pour reproduire ce problème dans la version de débogage demain et voir si je peux comprendre ce qui se passe. Si je ne trouve pas le temps, je serai absent du bureau jusqu'après Noël.

Votre observation selon laquelle l'introduction d'une variable de type Action fait disparaître le problème est extrêmement intéressante. Le compilateur maintient de nombreux caches à la fois pour des raisons de performance et pour l'analyse requise par la spécification du langage. Les lambdas et les variables locales, en particulier, ont une logique de mise en cache très complexe. Je serais prêt à parier jusqu'à un dollar qu'un cache est initialisé ou rempli de manière incorrecte ici, et que l'utilisation de la variable locale remplit la bonne valeur dans le cache.

Merci pour le rapport !

MISE À JOUR : je suis maintenant dans le bus et cela m'est venu à l'esprit ; je pense savoir exactement ce qui ne va pas. Le compilateur est perezoso en particulier lorsqu'il s'agit de types provenant de métadonnées. La raison en est qu'il peut y avoir des centaines de milliers de types dans les assemblages référencés et qu'il n'est pas nécessaire de charger des informations sur chacun d'entre eux. Vous allez probablement utiliser beaucoup moins de 1% d'entre eux, alors ne gaspillons pas beaucoup de temps et de mémoire à charger des choses que vous n'utiliserez jamais. En fait, la paresse est plus profonde que cela ; un type passe par plusieurs "étapes" avant de pouvoir être utilisé. D'abord son nom est connu, puis son type de base, puis si sa hiérarchie de type de base est bien fondée (acyclique, etc.), puis ses contraintes de paramètres de type, puis ses membres, puis si les membres sont bien fondés (que les surcharges surchargent quelque chose de la même signature, et ainsi de suite). leur membres connus", avant de vérifier la signature de l'invocation du délégué pour la compatibilité. Mais le code qui rend une variable locale probablement hace faire ça. Je pense que lors de la vérification de la conversion, le type Action peut même ne pas avoir de méthode invoke pour le compilateur.

Nous le saurons bientôt.

MISE À JOUR : Mes pouvoirs psychiques sont forts ce matin. Lorsque la résolution de surcharge tente de déterminer s'il existe une méthode "Invoke" du type délégué qui prend zéro argument, elle trouve zéro méthode d'invocation au choix . Nous devrions nous assurer que les métadonnées du type de délégué sont entièrement chargées avant de procéder à la résolution des surcharges. Il est étrange que cela soit passé inaperçu aussi longtemps ; cela se reproduit en C# 3.0. Bien sûr, il ne se reproduit pas en C# 2.0 simplement parce qu'il n'y avait pas de lambdas ; les méthodes anonymes en C# 2.0 exigent que vous indiquiez le type explicitement, ce qui crée un local, qui, nous le savons, charge les métadonnées. Mais j'imagine que la cause première du bogue - le fait que la résolution des surcharges ne force pas le chargement des métadonnées pour l'invocation - remonte au C# 1.0.

Quoi qu'il en soit, un bug fascinant, merci pour le rapport. Vous avez manifestement trouvé une solution de contournement. Je vais demander au service d'assurance qualité de suivre le problème à partir d'ici et nous essaierons de le corriger pour C# 5. Service Pack 1, qui est déjà en version bêta. .)

23voto

Femaref Points 41959

C'est probablement un problème d'inférence de type, apparemment le compilateur infère a comme un Action<T> au lieu de Action (il pourrait penser a es ActionSurrogate ce qui correspondrait à la Action<Action>> signature). Essayez de spécifier le type de a explicitement :

    ActionSurrogate b = (Action a) =>
                        {
                            a();
                        };

Si ce n'est pas le cas, vérifiez dans votre projet s'il n'y a pas d'éléments auto-définis. Action délégués prenant un paramètre.

2voto

Amit Bagga Points 442
    public static void ThisWontCompile()
        {
            ActionSurrogate b = (Action a) =>
            {
                a();
            };

        }

Ceci va compiler. Un problème avec le compilateur est qu'il ne peut pas trouver le délégué d'action sans paramètres. C'est pourquoi vous obtenez l'erreur.

public delegate void Action();
public delegate void Action<T>();
public delegate void Action<T1,T2>();
public delegate void Action<T1,T2,T3>();
public delegate void Action<T1,T2,T3,T4>();

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X