2010-05-18 14 views
4

Il me semble qu'il y a un bug/une inconsistance dans le compilateur C#.C# méthode générique params bug bug?

Cela fonctionne bien (première méthode est appelée):

public void SomeMethod(string message, object data); 
public void SomeMethod(string message, params object[] data); 

// .... 
SomeMethod("woohoo", item); 

Pourtant, ce qui provoque « L'appel est ambigu entre les méthodes suivantes » Erreur:

public void SomeMethod<T>(string message, T data); 
public void SomeMethod<T>(string message, params T[] data); 

// .... 
SomeMethod("woohoo", (T)item); 

je pouvais utiliser la décharge du première méthode entièrement, mais puisqu'il s'agit d'une bibliothèque très sensible aux performances et que la première méthode sera utilisée environ 75% du temps, je préfèrerais ne pas toujours envelopper les choses dans un tableau et instancier un itérateur pour passer en revue s'il y a seulement un article.

Diviser en différentes méthodes nommées serait désordonné au mieux IMO.

Pensées?

EDIT:

Je pense que Andrew pourrait être sur quelque chose.

Exemple complet:

public static class StringStuffDoer 
{ 
    public static string ToString<T>(T item1, T item2) 
    { 
     return item2.ToString() + item1.ToString(); 
    } 

    public static string ToString<T>(T item, params T[] items) 
    { 
     StringBuilder builder = new StringBuilder(); 

     foreach (T currentItem in items) 
     { 
      builder.Append(currentItem.ToString()); 
     } 

     return item.ToString() + builder.ToString(); 
    } 

    public static void CallToString() 
    { 
     ToString("someString", null); // FAIL 
     ToString("someString", "another string"); // SUCCESS 
     ToString("someString", (string)null); // SUCCESS 
    } 
} 

Je pense toujours qu'il est étrange que le casting est nécessaire - l'appel est pas ambigu. Cela fonctionne si vous remplacez T par une chaîne ou un objet ou par un type non générique, alors pourquoi cela ne fonctionnerait-il pas pour le générique? Il trouve correctement les deux méthodes de correspondance possibles, donc je crois que par la spécification, il devrait choisir celui qui n'utilise pas de paramètres si possible. Corrigez-moi si je me trompe ici.

(pas) MISE A JOUR FINAL:

Désolé pour vous les gars prendre sur ce tyraid, je l'ai apparemment les yeux fixés sur ce trop long ... trop regardant génériques et params pour une nuit. La version non générique jette également l'erreur ambiguë, je viens d'avoir la signature de la méthode désactivée dans mon test de maquette.

FINALE REAL MISE A JOUR:

D'accord, c'est pourquoi le problème ne se présente pas dans mon test non générique. J'utilisais "object" comme paramètres de type. SomeMethod (object) et SomeMethod (params object []) ne lancent PAS l'erreur ambiguë, je suppose que "null" est automatiquement converti en "object". Un peu étrange je dirais, mais peut-être un peu compréhensible.

Alors, assez curieusement, cet appel ne fonctionne:

SomeMethod<object>("someMessage", null); 
+2

est l'article null? : s –

+6

Veuillez poster un programme court mais complet démontrant le problème, et dites-nous quelle version de C# vous utilisez. –

+0

Comment ça marche si vous ne jetez pas d'objet dans T? Si, à un moment donné, vous aviez déclaré un élément en tant que type T, cela fonctionne-t-il comme prévu? –

Répondre

3

semble fonctionner pour moi, fait le reste de votre look de code comme ce qui suit? Compile bien sur .NET 3.5 et sort "premier" puis "second" lors de l'exécution. Quelle version utilisez-vous/ciblez-vous?

EDIT

La question de votre code ci-dessus est que le compilateur ne peut pas dire quel type null est. Il est également valable de supposer qu'il s'agit d'une chaîne ou d'un tableau de chaînes, d'où le fait qu'il soit ambigu lorsqu'il est juste nul et non ambigu lorsque vous le lancez spécifiquement (c.-à-d.vous dites au compilateur comment il doit être traité).

MISE À JOUR

"The same can be argued for SomeMethod(string, string) and SomeMethod (string, params string[]), yet it works"

En fait, non ce ne est pas. Vous obtenez le même problème de méthode ambiguë.

public static string TypedToString(string item1, string item2) 
{ 
    return ""; 
} 

public static string TypedToString(string item1, params string[] items) 
{ 
    return ""; 
} 

public static void CallToString() 
{ 
    TypedToString("someString", null); // FAIL 
} 
+0

Désolé, j'ai mis à jour mon message pour refléter le problème. Le comportement est incohérent lorsque la valeur params est null sans cast. –

+0

La même chose peut être argumentée pour SomeMethod (string, string) et SomeMethod (string, params string []), mais cela fonctionne. –

+0

Il infère les types correctement, car il ramasse les deux méthodes de correspondance et dit "ces deux correspondent." Le problème est l'incohérence à ne pas choisir la méthode non-params dans le cas générique v.s. il suffit de choisir la méthode non-params dans le cas non-générique. –

2

Vous devez modifier les signatures pour être plus précis. Par exemple:

void Foo(object o1); 
void Foo(object o1, object o2); 
void Foo(object o1, object o2, object o3, params object[] rest); 

Notez la différence entre les deux derniers. Ceci résout le problème d'ambiguïté (fonctionnera aussi pour les génériques).

Mise à jour:

public void SomeMethod<T>(string message, T data); 
public void SomeMethod<T>(string message, T data1, T data2, params T[] data); 

seulement 2 méthodes. Aucune ambiguïté.

+0

Je ne devrais pas avoir à écrire la méthode supplémentaire cependant. SomeMethod (string, string) et SomeMethod (string, params string []) s'en sortent très bien. –

+0

Vous n'avez pas besoin de la méthode 'Foo' supplémentaire, c'était juste un exemple. Vous avez encore un appel de méthode ambigu, ce qu'il appellera réellement, l'implémentation est définie, et je ne négocierais pas sur un tel comportement. – leppie

+0

Je ne suis pas sûr de ce que vous dites - le comportement est bien défini - si possible, choisissez la méthode non-params qui correspond.Il le fait dans les non-génériques, même si je dis SomeMethod ("string", null), même si techniquement c'est un appel ambigu. L'incohérence est le seul problème ici. –

15

It appears to me as though there is a bug/inconsistency in the C# compiler.

Il existe certainement des bogues et des incohérences dans le compilateur. Vous n'en avez pas trouvé. Le compilateur se comporte complètement correctement et selon les spécifications dans tous ces cas. Je fais de mon mieux pour donner un sens à cette question très confuse. Laissez-moi essayer de décomposer en une série de questions.

Why does this succeed and call the first method?

public void SomeMethod(string message, object data); 
public void SomeMethod(string message, params object[] data); 
// .... 
SomeMethod("woohoo", item); 

(Présomption: cet élément est une expression d'un type de compilation objet autre que [].)

résolution de surcharge doit choisir entre deux méthodes applicables. La deuxième méthode n'est applicable que sous sa forme étendue. Une méthode applicable uniquement dans sa forme développée est automatiquement pire qu'une méthode applicable dans sa forme normale. Par conséquent, la meilleure méthode restante est choisie.

Why does this fail with an ambiguity error?

public void SomeMethod<T>(string message, T data); 
public void SomeMethod<T>(string message, params T[] data); 
// ... 
SomeMethod("woohoo", (T)item); 

Il est impossible de dire parce que vous ne dites pas ce que "T" est. D'où vient T dans cet exemple? Il y a deux types de paramètres nommés T déclarés; est ce code dans le contexte d'une de ces méthodes? Comme ce sont différents types tous les deux nommés T cela pourrait faire une différence. Ou est-ce encore un troisième type appelé T?

Puisque la question n'a pas assez d'information pour y répondre, je vais poser une meilleure question en votre nom.

Why does this fail with an ambiguity error?

public void SomeMethod<T>(string message, T data); 
public void SomeMethod<T>(string message, params T[] data); 
// ... 
SomeMethod("woohoo", "hello"); 

Il ne fonctionne pas. Ça réussit. L'inférence de type choisit "chaîne" pour T dans les deux méthodes. Les deux méthodes génériques sont applicables; le second est applicable dans sa forme élargie, de sorte qu'il perd.

OK, then why does this fail with an ambiguity error?

public void SomeMethod<T>(string message, T data); 
public void SomeMethod<T>(string message, params T[] data); 
// ... 
SomeMethod("woohoo", null); 

Il ne fonctionne pas. Il échoue avec une erreur "can not infer T". Il n'y a pas assez d'informations ici pour déterminer ce que T est dans les deux cas.Comme l'inférence de type ne parvient pas à trouver une méthode candidate, l'ensemble candidat est vide et la résolution de surcharge n'a rien à choisir.

So this succeeds because... ?

public void SomeMethod<T>(string message, T data); 
public void SomeMethod<T>(string message, params T[] data); 
// ... 
SomeMethod("woohoo", (string)null); 

inférence de type infère que les deux méthodes sont candidats lorsqu'ils sont construits avec "string". Encore une fois, la deuxième méthode est pire car elle n'est applicable que sous sa forme élargie.

What if we take type inference out of the picture? Why is this ambiguous?

public void SomeMethod<T>(string message, T data); 
public void SomeMethod<T>(string message, params T[] data); 
// ... 
SomeMethod<string>("woohoo", null); 

Nous avons maintenant trois candidats applicables. La première méthode, la deuxième méthode dans sa forme normale, et la deuxième méthode dans sa forme développée. La forme développée est ignorée car étendue est pire que la normale. Cela laisse deux méthodes dans leur forme normale, l'une prenant la chaîne et l'autre prenant la chaîne []. Ce qui est mieux?

Face à ce choix, nous choisissons toujours celui qui a le type le plus spécifique. Si vous avez dit

public void M(string s) { ... } 
public void M(object s) { ... } 
... 
M(null); 

que nous avions choisi la version de chaîne parce que la chaîne est plus spécifique que l'objet. Chaque chaîne est un objet mais pas tous les objets sont des chaînes.

chaîne n'est pas convertible en chaîne []. string [] n'est pas convertible en chaîne. Aucun n'est plus spécifique que l'autre. Par conséquent, ceci est une erreur d'ambiguïté; il y a deux "meilleurs" candidats.

Then why does this succeed and what does it do?

public void SomeMethod<T>(string message, T data); 
public void SomeMethod<T>(string message, params T[] data); 
// ... 
SomeMethod<object>("woohoo", null); 

Encore une fois nous avons trois candidats, pas deux. Nous éliminons automatiquement la forme développée comme avant, en laissant deux. Des deux méthodes en forme normale qui restent, ce qui est mieux?

Nous devons déterminer lequel est le plus spécifique. Chaque tableau d'objets est un objet, mais chaque objet n'est pas un tableau d'objets. object [] est plus spécifique que object, nous choisissons donc d'appeler la version qui prend un objet []. Nous passons un tableau de paramètres null, qui est presque certainement pas ce que vous vouliez.

C'est pourquoi il est extrêmement difficile de programmer des surcharges comme vous le faites. Vous présentez le potentiel pour vos utilisateurs de tomber dans toutes sortes d'ambiguïtés fous quand vous faites ce genre de choses. S'il vous plaît ne pas concevoir des méthodes similaires.

Une meilleure façon de concevoir ce genre de logique est la suivante: (Notez que je ne l'ai pas fait compilé ce code, ceci est juste à côté du haut de ma tête.)

static string ToString<T>(T t) 
{ 
    return t == null ? "" : t.ToString(); 
} 
static string ToString<T>(T t1, T t2) 
{ 
    return ToString<T>(t1) + ToString<T>(t2); 
} 
static string ToString<T>(T t1, T t2, params T[] rest) 
{ 
    string firstTwo = ToString<T>(t1, t2); 
    if (rest == null) return firstTwo; 
    var sb = new StringBuilder(); 
    sb.Append(firstTwo); 
    foreach(T t in rest) 
     sb.Append(ToString<T>(t)); 
    return sb.ToString(); 
} 

Maintenant, chaque cas est traité avec une sémantique raisonnable et une efficacité décente. Et pour tout site d'appel donné, vous pouvez immédiatement prédire avec précision quelle méthode sera appelée; il n'y a que trois possibilités: un argument, deux arguments ou plus de deux arguments. Chacun est géré sans ambiguïté par une méthode particulière.

+0

Merci pour votre réponse très complète, mais je suis en train de concevoir après un modèle que j'ai obtenu à partir du .NET Framework lui-même - la chaîne. Surcharges de format. String.Format (string, object) et string.Format (string, params object []) sont techniquement ambigus quand ils sont appelés string.Format ("format", null), mais il prend le premier. Je comprends pourquoi maintenant, mais la source de mes problèmes était à tort de penser que le comportement fonctionnait aussi pour d'autres classes que l'objet. La seule raison pour laquelle cela fonctionne pour object est que object [] peut être implicitement converti en object, ce qui n'est évidemment pas le cas pour les autres types. –

+0

@Mike: En effet, je souhaite que le format n'ait pas été conçu de cette façon; Je trouve cela déroutant. Cependant, c'est un défaut relativement minime dans le grand schéma des choses. –

0

Je veux ajouter cette friandise pour tous ceux qui viennent à travers cela pour expliquer la question de l'ambiguïté avec SomeMethod (objet) et SomeMethod (objet params []) qui me jeter de. Cela a été déterré de la spécification C# pour moi sur les forums MSDN par Figo Fei:

10.6.1.4 Tableaux de paramètres

Un paramètre déclaré avec un modificateur params est un tableau de paramètres. Lors de l'exécution d'une résolution de surcharge, une méthode avec une matrice de paramètres peut être applicable sous sa forme normale ou sous sa forme développée (§7.4.3.1). La forme développée d'une méthode n'est disponible que si la forme normale de la méthode n'est pas applicable et seulement si une méthode avec la même signature que le formulaire développé n'est pas déjà déclarée dans le même type.

Lorsque le type d'un tableau de paramètres est object [], une ambiguïté potentielle se produit entre la forme normale de la méthode et la forme utilisée pour un seul paramètre d'objet. La raison de l'ambiguïté est qu'un objet [] est lui-même implicitement convertible en objet type. Cependant, l'ambiguïté ne présente aucun problème, car elle peut être résolue en insérant une distribution si nécessaire.