461 votes

Pourquoi C# ne permet-il pas aux méthodes statiques d'implémenter une interface ?

Pourquoi le C# a-t-il été conçu de cette manière ?

D'après ce que j'ai compris, une interface ne fait que décrire un comportement, et sert à décrire une obligation contractuelle pour les classes qui implémentent l'interface de mettre en œuvre un certain comportement.

Si les classes souhaitent implémenter ce comportement dans une méthode partagée, pourquoi ne le feraient-elles pas ?

Voici un exemple de ce que j'ai en tête :

// These items will be displayed in a list on the screen.
public interface IListItem {
  string ScreenName();
  ...
}

public class Animal: IListItem {
    // All animals will be called "Animal".
    public static string ScreenName() {
        return "Animal";
    }
....
}

public class Person: IListItem {

    private string name;

    // All persons will be called by their individual names.
    public string ScreenName() {
        return name;
    }

    ....

 }

6 votes

Eh bien, Java 8 l'a fait ( stackoverflow.com/questions/23148471/ ).

1 votes

Voyez comment vous pouvez combiner un comportement statique avec l'héritage ou la mise en œuvre d'une interface : stackoverflow.com/a/13567309/880990

1 votes

IListItem.ScreenName() => ScreenName() (en utilisant la syntaxe C# 7) implémentera explicitement la méthode de l'interface en appelant la méthode statique. Les choses se gâtent lorsque vous ajoutez l'héritage à cela, cependant (vous devez réimplémenter l'interface).

225voto

Chris Marasti-Georg Points 17023

En supposant que vous vous demandez pourquoi vous ne pouvez pas le faire :

public interface IFoo {
    void Bar();
}

public class Foo: IFoo {
    public static void Bar() {}
}

Cela n'a pas de sens pour moi, sémantiquement. Les méthodes spécifiées sur une interface devraient être là pour spécifier le contrat d'interaction avec un objet. Les méthodes statiques ne vous permettent pas d'interagir avec un objet. Si vous vous trouvez dans une situation où votre implémentation pourrait être rendue statique, vous devez vous demander si cette méthode appartient vraiment à l'interface.


Pour mettre en œuvre votre exemple, je donnerais à Animal une propriété const, ce qui lui permettrait encore d'être accessible à partir d'un contexte statique, et je renverrais cette valeur dans l'implémentation.

public class Animal: IListItem {
    /* Can be tough to come up with a different, yet meaningful name!
     * A different casing convention, like Java has, would help here.
     */
    public const string AnimalScreenName = "Animal";
    public string ScreenName(){ return AnimalScreenName; }
}

Pour une situation plus complexe, vous pouvez toujours déclarer une autre méthode statique et la déléguer. En essayant de trouver un exemple, je n'ai trouvé aucune raison de faire quelque chose de non trivial à la fois dans un contexte statique et dans un contexte d'instance, donc je vous épargnerai un blob de FooBar, et le prendrai comme une indication que ce n'est peut-être pas une bonne idée.

7 votes

Un exemple parfait ! Mais je ne suis pas sûr de comprendre votre raisonnement. Le compilateur doit sûrement pourrait ont été conçues pour examiner également les membres des statues ? Les instances ne disposent-elles pas d'une table d'adresses pour l'implémentation de ces méthodes ? Les méthodes statiques ne pourraient-elles pas être incluses dans ce tableau ?

0 votes

Je me suis rendu compte au fur et à mesure que je clarifiais la situation que ce que vous demandez pourrait certainement être mis en œuvre par le compilateur. J'ai donc modifié ma réponse pour mieux répondre à la question "Pourquoi ?".

12 votes

Il y a un cas où cela pourrait être utile. Par exemple, je veux que tous les implémenteurs implémentent une méthode GetInstance qui prend un argument XElement. Je ne peux ni la définir comme une méthode statique dans l'interface, ni exiger une signature de constructeur de l'interface.

178voto

Mark Brackett Points 46824

Ma raison technique (simplifiée) est que les méthodes statiques ne font pas partie de l'environnement de l'entreprise. vtable et le site d'appel est choisi au moment de la compilation. C'est la même raison pour laquelle vous ne pouvez pas avoir de membres statiques surchargés ou virtuels. Pour plus de détails, vous aurez besoin d'un diplômé de CS ou d'un compilateur - ce que je ne suis ni l'un ni l'autre.

Pour la raison politique, je vais Citation d'Eric Lippert (qui est un compilateur wonk, et détient une licence en mathématiques, informatique et mathématiques appliquées de l'Université de Waterloo (source : LinkedIn ) :

...le principe de conception central des méthodes statiques, le principe qui leur donne leur nom... [est]... on peut toujours déterminer exactement, au moment de la compilation, quelle méthode sera appelée. C'est-à-dire que la méthode peut être résolue uniquement par l'analyse statique du code.

Notez que Lippert laisse une place à une méthode dite de type :

C'est-à-dire une méthode associée à un type (comme une statique), qui ne prend pas d'argument "this" non nul (contrairement à une instance ou une virtuelle), mais une méthode dont l'appel dépend du type construit de T (contrairement à une statique, qui doit pouvoir être déterminée au moment de la compilation).

mais doit encore être convaincu de son utilité.

5 votes

Excellent, c'est la réponse que je voulais écrire - je ne connaissais simplement pas le détail de la mise en œuvre.

5 votes

Excellente réponse. Et je veux cette "méthode des types" ! Elle serait utile en de nombreuses occasions (pensez aux métadonnées d'un type/classe).

25 votes

C'est la bonne réponse. Vous passez une interface à quelqu'un, il doit savoir comment appeler une méthode. Une interface est juste un table des méthodes virtuelles . Votre classe statique n'a pas cela. L'appelant ne saurait pas comment pour appeler une méthode. (Avant de lire cette réponse, je pensais que C# était simplement pédant. Maintenant je réalise qu'il s'agit d'une limitation technique, imposée par quelle interface es ). D'autres personnes vous diront que c'est une mauvaise conception. Ce n'est pas une mauvaise conception - c'est une limitation technique.

99voto

Ivan Arjentinski Points 299

La plupart des réponses ici semblent passer à côté de l'essentiel. Le polymorphisme peut être utilisé non seulement entre les instances, mais aussi entre les types. Ceci est souvent nécessaire, lorsque nous utilisons des génériques.

Supposons que nous ayons un paramètre de type dans une méthode générique et que nous devions effectuer une opération avec lui. Nous ne voulons pas faire d'instantanéité, car nous ne connaissons pas les constructeurs.

Par exemple :

Repository GetRepository<T>()
{
  //need to call T.IsQueryable, but can't!!!
  //need to call T.RowCount
  //need to call T.DoSomeStaticMath(int param)
}

...
var r = GetRepository<Customer>()

Malheureusement, je ne peux proposer que des alternatives "moches" :

  • Utiliser la réflexion Moche et qui bat l'idée des interfaces et du polymorphisme.

  • Créer une classe d'usine complètement distincte

    Cela pourrait augmenter considérablement la complexité du code. Par exemple, si nous essayons de modéliser des objets de domaine, chaque objet aurait besoin d'une autre classe de référentiel.

  • Instanciez et appelez ensuite la méthode de l'interface souhaitée.

    Cela peut être difficile à mettre en œuvre même si nous contrôlons la source des classes, utilisées comme paramètres génériques. La raison en est que, par exemple, nous pourrions avoir besoin que les instances soient uniquement dans un état bien connu, "connecté à la base de données".

Exemple :

public class Customer 
{
  //create new customer
  public Customer(Transaction t) { ... }

  //open existing customer
  public Customer(Transaction t, int id) { ... }

  void SomeOtherMethod() 
  { 
    //do work...
  }
}

afin d'utiliser l'instantination pour résoudre le problème de l'interface statique, nous devons faire la chose suivante :

public class Customer: IDoSomeStaticMath
{
  //create new customer
  public Customer(Transaction t) { ... }

  //open existing customer
  public Customer(Transaction t, int id) { ... }

  //dummy instance
  public Customer() { IsDummy = true; }

  int DoSomeStaticMath(int a) { }

  void SomeOtherMethod() 
  { 
    if(!IsDummy) 
    {
      //do work...
    }
  }
}

Cela est évidemment laid et complique inutilement le code de toutes les autres méthodes. Évidemment, ce n'est pas non plus une solution élégante !

35 votes

+1 pour "La plupart des réponses semblent passer à côté de l'essentiel" ; c'est incroyable comme il semble que presque toutes les réponses esquivent le cœur de la question pour se plonger dans un verbiage inutile...

0 votes

Vos exemples n'ont pas de sens pour moi... la fonctionnalité "IsQueryable" peut être une annotation/attribut, ou mieux encore, une interface que la classe implémente (sinon, comment l'interroger ?). RowCount... de quoi ? Le nombre de clients ? C'est quelque chose que le référentiel client devrait faire, pas la classe client. L'exemple des maths statiques est incomplet, donc je ne suis pas sûr de ce que vous essayez de faire ici.

5 votes

@Chris Voici un exemple spécifique qui m'a fait toucher cette restriction à nouveau. Je veux ajouter une interface IResettable aux classes pour indiquer qu'elles mettent en cache certaines données dans des variables statiques qui peuvent être réinitialisées par l'administrateur du site (par exemple, une liste de catégories de commandes, un ensemble de taux de TVA, une liste de catégories récupérées à partir d'une API externe) afin de réduire les accès à la base de données et aux API externes. Cela me permet d'automatiser la détection des classes qui peuvent être réinitialisées. Je peux toujours le faire, mais la méthode n'est pas imposée ou ajoutée automatiquement dans l'IDE et repose sur l'espoir.

20voto

supercat Points 25534

Je sais que c'est une vieille question, mais c'est intéressant. L'exemple n'est pas le meilleur. Je pense que ce serait beaucoup plus clair si vous montriez un cas d'utilisation :

string DoSomething<T>() where T:ISomeFunction
{
  if (T.someFunction())
    ...
}

Le simple fait de pouvoir disposer de méthodes statiques mettre en œuvre une interface ne permettrait pas d'obtenir ce que vous voulez ; ce qui serait nécessaire serait d'avoir des membres statiques en tant que partie d'une interface. Je peux certainement imaginer de nombreux cas d'utilisation pour cela, en particulier lorsqu'il s'agit de pouvoir créer des choses. Je pourrais proposer deux approches qui pourraient être utiles :

  1. Créez une classe générique statique dont le paramètre de type sera le type que vous passerez à DoSomething ci-dessus. Chaque variation de cette classe aura un ou plusieurs membres statiques contenant des informations relatives à ce type. Ces informations peuvent être fournies soit en demandant à chaque classe concernée d'appeler une routine d'enregistrement des informations, soit en utilisant Reflection pour obtenir les informations lorsque le constructeur statique de la variation de la classe est exécuté. Je crois que cette dernière approche est utilisée par des choses comme Comparer<T>.Default().
  2. Pour chaque classe T d'intérêt, définissez une classe ou une structure qui implémente IGetWhateverClassInfo<T> et satisfait à la contrainte "new". La classe ne contiendra pas réellement de champs, mais aura une propriété statique qui renvoie un champ statique avec les informations de type. Passez le type de cette classe ou structure à la routine générique en question, qui sera capable de créer une instance et de l'utiliser pour obtenir des informations sur l'autre classe. Si vous utilisez une classe dans ce but, vous devriez probablement définir une classe générique statique comme indiqué ci-dessus, pour éviter de devoir construire une nouvelle instance de descripteur-objet à chaque fois. Si vous utilisez une structure, le coût d'instanciation devrait être nul, mais chaque type de structure différent nécessitera une expansion différente de la routine DoSomething.

Aucune de ces approches n'est vraiment attrayante. D'un autre côté, je m'attendrais à ce que si les mécanismes existaient dans CLR pour fournir ce genre de fonctionnalité proprement, .net permettrait de spécifier des contraintes "new" paramétrées (puisque savoir si une classe a un constructeur avec une signature particulière semblerait être une difficulté comparable à celle de savoir si elle a une méthode statique avec une signature particulière).

14voto

John Kraft Points 4559

Les interfaces spécifient le comportement d'un objet.

Les méthodes statiques ne spécifient pas le comportement d'un objet, mais un comportement qui affecte un objet d'une certaine manière.

71 votes

Désolé Je ne suis pas sûr que ce soit correct ! Une interface ne spécifie pas le comportement. Une interface définit un ensemble d'opérations nommées. Deux classes peuvent implémenter la méthode d'une interface pour se comporter de manière complètement différente. Donc, une interface ne spécifie pas du tout le comportement. La classe qui l'implémente le fait.

1 votes

J'espère que vous ne pensez pas que je fais la fine bouche, mais je pense que c'est une distinction importante à comprendre pour quiconque apprend l'OO.

4 votes

Une interface est censée spécifier un contrat qui inclut le comportement et la présentation. C'est pourquoi il est interdit de modifier le comportement d'un appel d'interface, car les deux sont censés être fixes. Si vous avez une interface où l'appel agit différemment (par exemple, IList.Add a fait un retrait), ce ne serait pas correct.

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