37 votes

Pourquoi cette méthode d'extension générique ne se compile-t-elle pas?

Le code est un peu bizarre, si patient avec moi (gardez à l'esprit ce scénario ne viennent dans le code de production).

Dire que j'ai eu cette structure d'interface:

public interface IBase {  }
public interface IChild : IBase {  }

public interface IFoo<out T> where T : IBase {  }

Avec cette méthode d'extension de classe construit autour des interfaces:

public static class FooExt
{
    public static void DoSomething<TFoo>(this TFoo foo)
        where TFoo : IFoo<IChild>
    {
        IFoo<IChild> bar = foo;

        //foo.DoSomethingElse();    // Doesn't compile -- why not?
        bar.DoSomethingElse();      // OK
        DoSomethingElse(foo);       // Also OK!
    }

    public static void DoSomethingElse(this IFoo<IBase> foo)
    {
    }
}

Pourquoi ne pas l'commenté de ligne en DoSomething de la compilation? Le compilateur est parfaitement heureux de me laisser affecter foo de bar, qui est du même type que la contrainte générique, et d'appeler la méthode d'extension sur place. Il est également aucun problème à appeler la méthode d'extension sans la méthode d'extension de la syntaxe.

Quelqu'un peut confirmer si c'est un bug ou à un comportement attendu?

Merci!

Juste pour référence, voici le message d'erreur de compilation (types abrégée pour la lisibilité):

'TFoo' ne contient pas une définition pour 'DoSomethingElse" et la meilleure extension de surcharge de la méthode 'DoSomethingElse(IFoo)' a certains arguments invalides

9voto

Julien Lebosquain Points 20894

Citant la spécification C#:

7.6.5.2 Extension des invocations de méthode

Dans un appel de méthode (§7.5.5.1) de l'une des formes

expr . (identificateur )

expr . identificateur ( args )

expr . identificateur < typeargs > ( )

expr . identificateur < typeargs > ( args )

si le traitement normal de la l'invocation ne trouve pas applicable méthodes, une tentative est faite pour le processus de le construire comme une extension de la méthode invocation. Si expr ou à l'un des arguments a le type de compilation dynamique, les méthodes d'extension ne s'applique pas.

L'objectif est de trouver le meilleur type nom-de - C, de sorte que le correspondant statique d'invocation de méthode peut prendre lieu:

C . identificateur ( expr )

C . identificateur ( expr , args )

C . identificateur < typeargs > ( expr )

C . identificateur < typeargs > ( expr , args )

Une extension de la méthode Ci.Mj est admissible si:

· Ci est un non-générique, non-classe imbriquée

· Le nom de Mj est l'identificateur de

· Mj est accessible et applicable lorsqu'il est appliqué à la des arguments d'une méthode statique comme le montre ci-dessus

· Implicite de l'identité, de référence ou de boxe de conversion existe de expr pour le type de la première paramètre de Mj.

Depuis DoSomethingElse(foo) compile mais foo.DoSomethingElse() ne l'est pas, il semble comme un compilateur bug dans la résolution de surcharge pour les méthodes d'extension: une référence implicite de conversion existe à partir de l' foo de IFoo<IBase>.

5voto

oleksii Points 17099

Pouvez-vous définir DoSomethingElse dans l' IFoo ?

 public interface IFoo<out T> where T : IBase
{
    void DoSomethingElse();
}
 

METTRE À JOUR

Peut-être que vous pourrez alors changer la signature

 public static void DoSomethingElse(this IFoo<IBase> foo)
=>
public static void DoSomethingElse<TFoo>(this TFoo foo) 
    where TFoo : IFoo<IChild>
 

3voto

Ethan Cabiac Points 3669

J'ai trouvé des preuves qu'il s'agit d'un "bug".

Bien qu'il ne soit pas nécessaire qu'un langage CLR prenne en charge toutes les fonctionnalités disponibles dans MSIL, le fait est que ce que vous essayez de faire est valable dans MSIL.

Si vous vouliez transférer le code dans IL et donner à la méthode DoSomething l'apparence suivante:

 .method public hidebysig static void  DoSomething<(class TestLib.IFoo`1<class TestLib.IChild>) T>(!!T foo) cil managed
{
  .custom instance void [System.Core]System.Runtime.CompilerServices.ExtensionAttribute::.ctor() = ( 01 00 00 00 ) 
  // Code size       52 (0x34)
  .maxstack  1
  .locals init ([0] class TestLib.IFoo`1<class TestLib.IChild> bar)
  IL_0000:  nop
  IL_0001:  ldarg.0
  IL_0002:  box        !!T
  IL_0007:  call       void TestLib.Ext::DoSomethingElse(class TestLib.IFoo`1<class  TestLib.IBase>)
  IL_000c:  nop
  IL_000d:  ret
} // end of method Ext::DoSomething
 

vous découvririez que cela compile. Et qu'est-ce que le réflecteur résout comme en C #?

 public static void DoSomething<T>(this T foo) where T: IFoo<IChild>
{
    foo.DoSomethingElse();
}
 

1voto

default.kramer Points 3119

Je ne sais pas pourquoi il ne compile pas, mais est-ce une alternative acceptable?

 public static void DoSomethingElse<T>(this IFoo<T> foo) where T : IBase
{
}
 

1voto

Ssithra Points 639

Votre morceau de code

public static void DoSomethingElse(this IFoo<IBase> foo)
{
}

fait DoSomethingElse disponible uniquement sur IFoo<IBase> des cas, ce que toto n'est évidemment pas, puisque c'est un IFoo<IChild>. Le fait qu' IChild provient IBase ne font pas d' IFoo<IChild> dérivent IFoo<IBase>. Donc toto , malheureusement, ne peut être considéré comme une sorte d' IFoo<IBase>, et DoSomethingElse , par conséquent, ne peut être invoqué sur elle.

Mais ce problème peut facilement être évité si vous changer un peu de votre méthode d'extension de cette façon :

public static void DoSomethingElse<T>(this IFoo<T> foo) where T : IBase
{
}

Maintenant, il compile et tout fonctionne très bien.

La partie la plus intéressante est qu' DoSomethingElse(foo); compile en cas d'appel d'une méthode statique de la syntaxe, mais pas avec une méthode d'extension de la syntaxe. Évidemment, avec une méthode statique de style appel, générique de covariance fonctionne bien : l'argument foo est tapé comme un IFoo<IBase> , mais peuvent être affectés à un IFoo<IChild>, alors l'appel est d'accord. Mais comme une extension de la méthode, en raison de la façon dont elle est déclarée DoSomethingElse est faite uniquement disponible sur les instances formellement typé en tant que IFoo<IBase> , même si elle serait compatible avec IFoo<IChild>, de sorte que cette syntaxe ne fonctionne pas sur IFoo<IChild> des cas.

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