78 votes

Pourquoi le compilateur C # se plaint-il que "les types peuvent s'unifier" lorsqu'ils proviennent de classes de base différentes?

Ma non-compiler le code est similaire à ceci:

public abstract class A { }

public class B { }

public class C : A { }

public interface IFoo<T>
{
    void Handle(T item);
}

public class MyFoo<TA> : IFoo<TA>, IFoo<B>
    where TA : A
{
    public void Handle(TA a) { }
    public void Handle(B b) { }
}

Le compilateur C# refuse de compiler ce, en citant la règle suivante/erreur:

'MyProject.MyFoo<TA>' ne peut pas mettre en œuvre à la fois " MyProject.IFoo<TA> " et " MyProject.IFoo<Monprojet.B>', parce qu'ils peuvent unifier pour certains type de paramètre de substitution

Je comprends ce que cette erreur signifie; si TA pourrait être n'importe quoi à tous, alors il pourrait techniquement être aussi un B qui serait d'introduire de l'ambiguïté sur les deux Handle des implémentations.

Mais TA peut pas être quoi que ce soit. En fonction du type de hiérarchie, TA ne peut pas être un B - au moins, je ne pense qu'il peut. TA doit découler A, ce qui n'est pas dériver de l' B, et, évidemment, il n'y a pas plusieurs héritage de classe en C#/.NET.

Si je supprime le paramètre générique et remplacez - TA avec C, ou même A, il compile.

Alors, pourquoi ne j'obtiens cette erreur? Est-ce un bug ou général des nations-unies à l'intelligence du compilateur, ou est-il autre chose que je suis absent?

Est-il une solution ou suis-je tout simplement allez avoir à re-mettre en œuvre l' MyFoo classe générique comme un espace non-classe générique pour chaque possible TA type dérivé?

52voto

Eric Lippert Points 300275

C'est une conséquence de l'article 13.4.2 du C# 4 cahier des charges, qui stipule:

Si possible type construit créés à partir de C, d'après les arguments de type sont substitués en L, à cause de deux interfaces à L'identique, puis la déclaration de C n'est pas valide. Contrainte déclarations ne sont pas considérés lors de la détermination de tous les possibles de types construits.

Notez que la deuxième phrase.

Il n'est donc pas un bug du compilateur; le compilateur est correct. On pourrait argumenter qu'il s'agit d'une faille dans le langage de spécification.

En règle générale, les contraintes sont ignorées dans presque toutes les situations dans lesquelles un fait doit être déduit sur un type générique. Les contraintes sont surtout utilisés pour déterminer l' effectif de la classe de base d'un paramètre de type générique, et rien d'autre.

Malheureusement, cela conduit parfois à des situations où la langue est inutilement stricte, comme vous l'avez découvert.


C'est en général une mauvaise odeur de code pour mettre en œuvre le "même" de l'interface à deux reprises, d'une certaine façon distingue que par le générique de type d'arguments. C'est bizarre, par exemple, en class C : IEnumerable<Turtle>, IEnumerable<Giraffe> -- qu'est-ce que C qu'il est à la fois une séquence de tortues, et une séquence de girafes, en même temps? Pouvez-vous décrire la chose réelle que vous essayez de faire ici? Il y a peut être un meilleur modèle pour résoudre le vrai problème.


En fait, si votre interface est exactement comme vous le décrivez:

interface IFoo<T>
{
    void Handle(T t);
}

Ensuite, l'héritage multiple de l'interface présente un autre problème. Vous pouvez tout à fait décider de faire de cette interface contravariant:

interface IFoo<in T>
{
    void Handle(T t);
}

Maintenant, supposons que vous avez

interface IABC {}
interface IDEF {}
interface IABCDEF : IABC, IDEF {}

Et

class Danger : IFoo<IABC>, IFoo<IDEF>
{
    void IFoo<IABC>.Handle(IABC x) {}
    void IFoo<IDEF>.Handle(IDEF x) {}
}

Et maintenant, les choses deviennent vraiment fous...

IFoo<IABCDEF> crazy = new Danger();
crazy.Handle(null);

La mise en œuvre de la Poignée est appelé???

Voir cet article et les commentaires pour plus d'idées sur cette question:

http://blogs.msdn.com/b/ericlippert/archive/2007/11/09/covariance-and-contravariance-in-c-part-ten-dealing-with-ambiguity.aspx

8voto

Nawaz Points 148870

Apparemment, il était comme discuté lors de Microsoft Connect:

Et la solution de contournement est, de définir une autre interface que:

public interface IIFoo<T> : IFoo<T>
{
}

Puis appliquer ceci à la place:

public class MyFoo<TA> : IIFoo<TA>, IFoo<B>
    where TA : A
{
    public void Handle(TA a) { }
    public void Handle(B b) { }
}

Il regroupe des amendes, par mono.

2voto

Erik Eidt Points 1397

Voir ma réponse à fondamentalement la même question ici: http://stackoverflow.com/a/12361409/471129

Dans une certaine mesure, cela peut être fait! J'utilise une méthode de différenciation, au lieu de qualificatif(s) de limiter les types.

Il n'a pas d'unifier, en fait, il pourrait être mieux que si c'était parce que vous pouvez taquiner le séparer les interfaces d'intervalle.

Voir mon post ici, avec un travail entièrement exemple dans un autre contexte. http://stackoverflow.com/a/12361409/471129

Fondamentalement, ce que vous faire est d'ajouter un autre paramètre de type d' IIndexer, de sorte qu'il devient IIndexer <TKey, TValue, TDifferentiator>.

Ensuite, quand vous utilisez deux fois, vous passez "Première" pour la 1ère utilisation, et la "Seconde" pour la 2ème utilisation

Ainsi, la classe de Test devient: classe Test<TKey, TValue> : IIndexer<TKey, TValue, First>, IIndexer<TValue, TKey, Second>

Ainsi, vous pouvez le faire new Test<int,int>()

en cas de Première et Deuxième sont triviales:

interface First { }

interface Second { }

0voto

Simon Svensson Points 11667

Deviner maintenant ...

Est-ce que A, B et C ne peuvent pas être déclarés dans des assemblages extérieurs, où la hiérarchie des types peut changer après la compilation de MyFoo <T>, provoquant des ravages dans le monde?

La solution de contournement facile consiste simplement à implémenter Handle (A) au lieu de Handle (TA) (et à utiliser IFoo <A> au lieu de IFoo <TA>). De toute façon, vous ne pouvez pas faire beaucoup plus avec Handle (TA) que d’accéder aux méthodes de A (en raison de la contrainte A: TA).

 public class MyFoo : IFoo<A>, IFoo<B> {
    public void Handle(A a) { }
    public void Handle(B b) { }
}
 

0voto

luqui Points 26009

Hmm, qu'en est-il de ceci:

 public class MyFoo<TA> : IFoo<TA>, IFoo<B>
    where TA : A
{
    void IFoo<TA>.Handle(TA a) { }
    void IFoo<B>.Handle(B b) { }
}
 

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