Pourquoi C # n'autorise-t-il pas les méthodes statiques à implémenter une interface?

StackOverflow https://stackoverflow.com/questions/259026

  •  06-07-2019
  •  | 
  •  

Question

Pourquoi C # a-t-il été conçu de cette façon?

Si je comprends bien, une interface décrit uniquement le comportement et sert à décrire une obligation contractuelle pour les classes implémentant l'interface que certains comportements soient implémentés.

Si les classes souhaitent implémenter ce comportement dans une méthode partagée, pourquoi ne le devraient-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;
    }

    ....

 }
Était-ce utile?

La solution

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

public interface IFoo {
    void Bar();
}

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

Cela n’a aucun sens pour moi, sémantiquement. Les méthodes spécifiées sur une interface doivent ê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 position où votre implémentation pourrait devenir statique, vous devrez peut-être vous demander si cette méthode appartient réellement à l'interface.


Pour implémenter votre exemple, je donnerais à Animal une propriété const qui lui permettrait tout de même de pouvoir y accéder depuis un contexte statique et renverrait 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 compliquée, vous pouvez toujours déclarer une autre méthode statique et y déléguer des tâches. En essayant de trouver un exemple, je ne pouvais imaginer la moindre raison pour laquelle vous feriez quelque chose de non trivial dans un contexte à la fois statique et d'instance. Je vous épargne donc un blob FooBar, et le prends comme une indication du fait qu'il pourrait ne pas être une bonne idée.

Autres conseils

Ma raison technique (simplifiée) est que les méthodes statiques ne figurent pas dans la 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 ou virtuels. Pour plus de détails, vous aurez besoin d’un diplômé CS ou d’un compilateur wonk, ce que je ne suis ni l'un ni l'autre.

Pour des raisons politiques, je vais citation de Eric Lippert (compilateur de wonk, et titulaire d’un baccalauréat en mathématiques, informatique et mathématiques appliquées de l’Université de Waterloo (source: < a href = "https://www.linkedin.com/pub/eric-lippert/85/934/a38" rel = "noreferrer"> LinkedIn ):

  

... le principe de base des méthodes statiques, le principe qui leur donne leur nom ... [est] ... il est toujours possible de déterminer exactement, au moment de la compilation, quelle méthode sera appelée. Autrement dit, la méthode peut être résolue uniquement par une analyse statique du code.

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

  

C’est-à-dire une méthode associée à un type (comme une statique), qui ne prend pas une valeur non nullable & # 8220; this & # 8221; argument (contrairement à une instance ou à un virtuel), mais dans lequel la méthode appelée dépend du type de T construit (à la différence d’un statique qui doit pouvoir être déterminé à la compilation).

mais doit encore être convaincu de son utilité.

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

Supposons que nous ayons un paramètre de type dans la méthode générique et que nous devions effectuer certaines opérations avec celui-ci. Nous ne voulons pas créer d’instantané, 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 arriver qu'avec & "moche &"; alternatives:

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

  • Créer une classe d'usine complètement séparée

    Cela pourrait considérablement augmenter 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 puis appelez la méthode d'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 connues, & "; Connectées à la base de données &"; Etat.

Exemple:

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

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

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

pour utiliser l’instantination pour résoudre le problème de l’interface statique, nous devons procéder comme suit:

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...
    }
  }
}

C’est évidemment moche et inutile de compliquer le code pour toutes les autres méthodes. De toute évidence, pas une solution élégante non plus!

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 implémenter une interface ne donnerait pas 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, notamment lorsqu'il est possible de créer des choses. Deux approches que je pourrais proposer et qui pourraient être utiles:

  1. Créez une classe générique statique dont le paramètre type sera celui que vous transmettriez à DoSomething ci-dessus. Chaque variante de cette classe aura un ou plusieurs membres statiques contenant des éléments liés à ce type. Cette information pourrait être fournie soit en demandant à chaque classe d’intérêt d’appeler un & "Quot register information" & "; routine ou en utilisant Reflection pour obtenir les informations lorsque le constructeur statique de la variante de classe est exécuté. Je crois que cette dernière approche est utilisée par des choses comme Comparer & Lt; T & Gt; .Default ().
  2. Pour chaque classe T qui vous intéresse, définissez une classe ou une structure qui implémente IGetWurtherClassInfo < T > et satisfait à un " nouveau " contrainte. La classe ne contiendra pas réellement de champs, mais aura une propriété statique qui retourne un champ statique avec les informations de type. Transmettez le type de cette classe ou structure à la routine générique en question, qui pourra créer une instance et l'utiliser pour obtenir des informations sur l'autre classe. Si vous utilisez une classe à cette fin, vous devez probablement définir une classe générique statique, comme indiqué ci-dessus, pour éviter de devoir créer à chaque fois une nouvelle instance descripteur-objet. Si vous utilisez une structure, le coût d'instanciation doit être nul, mais chaque type de structure différent nécessitera une extension différente de la routine DoSomething.

Aucune de ces approches n’est vraiment attrayante. D'autre part, je m'attendrais à ce que si les mécanismes existants dans CLR fournissaient ce type de fonctionnalité proprement, .net permettrait de spécifier un & "Nouveau &" Paramétré. contraintes (puisque savoir si une classe a un constructeur avec une signature particulière semblerait être difficilement comparable à savoir si elle a une méthode statique avec une signature particulière).

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

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

Dans la mesure où les interfaces représentent des " contrats " ;, il semble assez raisonnable que les classes statiques implémentent des interfaces.

Les arguments ci-dessus semblent tous passer à côté de ce point concernant les contrats.

La myopie, je suppose.

Lors de la conception initiale, les interfaces étaient uniquement destinées à être utilisées avec des instances de classe

IMyInterface val = GetObjectImplementingIMyInterface();
val.SomeThingDefinedinInterface();

C’est seulement avec l’introduction d’interfaces que les contraintes pour les génériques l’ajout d’une méthode statique à une interface ont une utilisation pratique.

(en réponse au commentaire :) Je pense que le modifier maintenant nécessiterait une modification du CLR, ce qui entraînerait des incompatibilités avec les assemblys existants.

Etant donné que le but d’une interface est d’autoriser le polymorphisme, pouvoir transmettre une instance d’un nombre quelconque de classes définies ayant toutes été définies pour implémenter l’interface définie ... garantissant que, dans votre appel polymorphe, le code sera capable de trouver la méthode que vous appelez. cela n'a aucun sens de permettre à une méthode statique d'implémenter l'interface,

Comment l'appelleriez-vous ??

public interface MyInterface { void MyMethod(); }
public class MyClass: MyInterface
{
    public static void MyMethod() { //Do Something; }
}

 // inside of some other class ...  
 // How would you call the method on the interface ???
    MyClass.MyMethod();  // this calls the method normally 
                         // not through the interface...

    // This next fails you can't cast a classname to a different type... 
    // Only instances can be Cast to a different type...
    MyInterface myItf = MyClass as MyInterface;  

En ce qui concerne les méthodes statiques utilisées dans des contextes non génériques, je conviens qu'il n'est pas très logique de les autoriser dans des interfaces, car vous ne pourriez pas les appeler si vous aviez une référence à l'interface de toute façon. Cependant, il existe un vide fondamental dans la conception du langage créé en utilisant des interfaces PAS dans un contexte polymorphe, mais dans un contexte générique. Dans ce cas, l'interface n'est pas du tout une interface, mais plutôt une contrainte. Parce que C # n'a pas de concept de contrainte en dehors d'une interface, il manque des fonctionnalités substantielles. Exemple:

T SumElements<T>(T initVal, T[] values)
{
    foreach (var v in values)
    {
        initVal += v;
    }
}

Ici, il n'y a pas de polymorphisme, le générique utilise le type réel de l'objet et appelle l'opérateur + =, mais cela échoue car il ne peut pas affirmer avec certitude que cet opérateur existe. La solution simple consiste à le spécifier dans la contrainte; la solution simple est impossible car les opérateurs sont statiques et les méthodes statiques ne peuvent pas être dans une interface et (voici le problème) les contraintes sont représentées sous la forme d'interfaces.

Ce dont C # a besoin est un type de contrainte réel, toutes les interfaces seraient également des contraintes, mais toutes les contraintes ne seraient pas des interfaces alors vous pourriez le faire:

constraint CHasPlusEquals
{
    static CHasPlusEquals operator + (CHasPlusEquals a, CHasPlusEquals b);
}

T SumElements<T>(T initVal, T[] values) where T : CHasPlusEquals
{
    foreach (var v in values)
    {
        initVal += v;
    }
}

On a déjà beaucoup parlé de faire un IArithmetic pour tous les types numériques à implémenter, mais on s'inquiète de l'efficacité, car une contrainte n'est pas une construction polymorphe, ce qui résoudrait ce problème avec une contrainte CArithmetic.

Les interfaces étant dans une structure d'héritage, les méthodes statiques n'héritent pas bien.

Ce que vous semblez vouloir permettre à une méthode statique d’être appelée à la fois par le type ou par toute instance de ce type. Cela créerait au moins une ambiguïté qui n’est pas un trait désirable.

Il y aurait des débats interminables sur l’importance de cette question, sur la meilleure pratique à suivre et sur les problèmes de performances rencontrés d’une manière ou d’une autre. En ne le supportant pas, C # nous évite de nous en inquiéter.

Il est également probable qu'un compilateur qui se conformerait à ce désir perdrait certaines optimisations pouvant entraîner une séparation plus stricte entre les méthodes d'instance et les méthodes statiques.

Vous pouvez considérer les méthodes statiques et les méthodes non statiques d'une classe comme des interfaces différentes. Lorsqu'elles sont appelées, les méthodes statiques se résolvent en objet de classe singleton, et les méthodes non statiques, en instance de la classe que vous traitez. Donc, si vous utilisez des méthodes statiques et non statiques dans une interface, vous déclareriez en réalité deux interfaces alors que nous voulons réellement que les interfaces soient utilisées pour accéder à un élément cohérent.

Pour donner un exemple où il me manque une implémentation statique de méthodes d'interface ou ce que Mark Brackett a présenté en tant que & "méthode dite de type &";

Lors de la lecture d'un stockage de base de données, nous avons une classe DataTable générique qui gère la lecture d'une table de n'importe quelle structure. Toutes les informations spécifiques à la table sont placées dans une classe par table. Elle contient également les données d'une ligne de la base de données et doit implémenter une interface IDataRow. IDataRow contient une description de la structure de la table à lire dans la base de données. Le DataTable doit demander la structure de données à IDataRow avant d'être lu à partir de la base de données. Actuellement, cela ressemble à:

interface IDataRow {
  string GetDataSTructre();  // How to read data from the DB
  void Read(IDBDataRow);     // How to populate this datarow from DB data
}

public class DataTable<T> : List<T> where T : IDataRow {

  public string GetDataStructure()
    // Desired: Static or Type method:
    // return (T.GetDataStructure());
    // Required: Instantiate a new class:
    return (new T().GetDataStructure());
  }

}

GetDataStructure n’est requis qu’une fois par chaque table à lire, la surcharge pour l’instanciation d’une instance supplémentaire est minime. Cependant, ce serait bien dans ce cas ici.

FYI: Vous pouvez obtenir un comportement similaire à celui que vous souhaitez en créant des méthodes d'extension pour l'interface. La méthode d'extension serait un comportement statique partagé, non remplaçable. Malheureusement, cette méthode statique ne ferait pas partie du contrat.

Les interfaces sont des ensembles abstraits de fonctionnalités disponibles définies.

Qu'une méthode de cette interface se comporte ou non de manière statique est un détail d'implémentation qui doit être caché derrière l'interface . Il serait faux de définir une méthode d'interface comme statique car vous seriez obligé inutilement d'implémenter la méthode d'une certaine manière.

Si les méthodes étaient définies comme statiques, la classe implémentant l'interface ne serait pas aussi encapsulée qu'elle pourrait l'être. L’encapsulation est une bonne chose à faire dans la conception orientée objet (je n’expliquerai pas pourquoi, vous pouvez le lire ici: http://en.wikipedia.org/wiki/Object-oriented ). Pour cette raison, les méthodes statiques ne sont pas autorisées dans les interfaces.

Les classes statiques devraient pouvoir le faire pour pouvoir être utilisées de manière générique. J'ai dû mettre en place un Singleton pour obtenir les résultats souhaités.

J'avais plusieurs classes de classes statiques qui implémentaient des méthodes CRUD telles que & "Créer &", & "Lire &"; & "Mettre à jour & ";" & "Supprimer &"; Pour chaque type d'entité comme & "Utilisateur &"; & "Équipe &"; etc., j'ai ensuite créé un contrôle de base doté d'une propriété abstraite pour la classe de couche de gestion qui implémentait les méthodes CRUD. Cela m'a permis d'automatiser & "Créer &", & "Lire &", & "Mettre à jour &", & "Supprimer quot; opérations de la classe de base. J'ai dû utiliser un singleton à cause de la limitation statique.

La plupart des gens semblent oublier que, dans la POO, les classes sont également des objets. Elles ont donc des messages qui, pour une raison quelconque, sont appelées c # par & "méthode statique &"; Le fait qu'il existe des différences entre les objets d'instance et les objets de classe ne montre que des défauts ou des faiblesses dans le langage. Optimiste à propos de c # si ...

OK, voici un exemple de besoin d’une 'méthode de type'. Je suis en train de créer une classe parmi un ensemble de classes basées sur du XML source. J'ai donc un

  static public bool IsHandled(XElement xml)

fonction appelée à tour de rôle sur chaque classe.

La fonction doit être statique, sinon nous perdons du temps à créer des objets inappropriés. Comme @Ian Boyde le fait remarquer, cela pourrait être fait dans une classe d'usine, mais cela ne fait qu'ajouter à la complexité.

Il serait bien de l'ajouter à l'interface pour forcer les développeurs de classe à l'implémenter. Cela ne causerait pas de surcharge significative: il s’agit uniquement d’une vérification du temps de compilation / liaison et n’affecte pas la vtable.

Cependant, ce serait également une amélioration assez mineure. Comme la méthode est statique, en tant qu'appelant, je dois l'appeler explicitement et obtenir une erreur de compilation immédiate si elle n'est pas implémentée. Le fait de le spécifier sur l'interface signifierait que cette erreur survient de manière marginale plus tôt dans le cycle de développement, mais ceci est trivial par rapport à d'autres problèmes d'interface endommagée.

Il s’agit donc d’une caractéristique potentielle mineure qui, dans l’ensemble, vaut probablement mieux la laisser de côté.

Le fait qu’une classe statique soit implémentée en C # par Microsoft, ce qui crée une instance spéciale d’une classe avec les éléments statiques, n’est qu’une bizarrerie de la manière dont la fonctionnalité statique est réalisée. Ce n’est pas un point théorique.

Une interface DEVRAIT être un descripteur de l’interface de classe - ou de la façon dont elle est interagie, et qui devrait inclure des interactions statiques. La définition générale d'interface (de Meriam-Webster): l'endroit ou la zone où différentes choses se rencontrent et communiquent ou se touchent. Lorsque vous omettez entièrement les composants statiques d'une classe ou des classes statiques, nous ignorons de grandes sections de la manière dont ces mauvais garçons interagissent.

Voici un exemple très clair de la possibilité d'utiliser des interfaces avec des classes statiques:

public interface ICrudModel<T, Tk>
{
    Boolean Create(T obj);
    T Retrieve(Tk key);
    Boolean Update(T obj);
    Boolean Delete(T obj);
}

Actuellement, j'écris les classes statiques qui contiennent ces méthodes sans aucune sorte de vérification pour m'assurer que je n'ai rien oublié. C’est comme au mauvais vieux temps de la programmation avant la POO.

C # et le CLR doivent prendre en charge les méthodes statiques dans les interfaces, comme le fait Java. Le modificateur statique fait partie d'une définition de contrat et a une signification, en particulier le fait que le comportement et la valeur de retour ne varient pas d'une instance à l'autre bien qu'ils puissent toujours varier d'un appel à l'autre.

Cela dit, je vous recommande d'utiliser une annotation à la place lorsque vous souhaitez utiliser une méthode statique dans une interface. Vous obtiendrez la fonctionnalité que vous recherchez.

Je pense que la réponse courte est & ", car il n’a aucune utilité &"; Pour appeler une méthode d'interface, vous avez besoin d'une instance du type. À partir de méthodes d'instance, vous pouvez appeler les méthodes statiques souhaitées.

Je pense que la question concerne le fait que C # a besoin d'un autre mot clé, précisément pour ce type de situation. Vous voulez une méthode dont la valeur de retour dépend uniquement du type sur lequel elle est appelée. Vous ne pouvez pas l'appeler & "Statique &"; si ce type est inconnu. Mais une fois que le type sera connu, il deviendra statique. " Statiques non résolues " est l’idée - ce n’est pas encore statique, mais une fois que nous connaîtrons le type de destinataire, ce le sera. C’est un très bon concept, c’est pourquoi les programmeurs ne cessent de le demander. Mais cela ne correspond pas tout à fait à la façon dont les concepteurs ont pensé la langue.

Comme ce n’est pas disponible, j’ai commencé à utiliser des méthodes non statiques comme indiqué ci-dessous. Pas vraiment idéal, mais je ne vois pas d’approche plus logique, du moins pas pour moi.

public interface IZeroWrapper<TNumber> {
  TNumber Zero {get;}
}

public class DoubleWrapper: IZeroWrapper<double> {
  public double Zero { get { return 0; } }
}
  

Selon le concept orienté objet Interface implémentée par les classes et   avoir un contrat pour accéder aux fonctions (ou méthodes) implémentées en utilisant   objet.

Donc, si vous souhaitez accéder aux méthodes de contrat d'interface, vous devez créer un objet. C'est toujours ce qui n'est pas permis dans le cas de méthodes statiques. Les classes statiques, les méthodes et les variables ne nécessitent jamais d'objets et ne se chargent pas en mémoire sans créer d'objet de cette zone (ou classe), ou vous pouvez dire que vous n'avez pas besoin de créer un objet.

Conceptuellement , aucune interface ne peut définir un contrat incluant des méthodes statiques.

Pour l'implémentation actuelle du langage C #, la restriction est due à la tolérance d'héritage d'une classe de base et d'interfaces. Si & Quot; class SomeBaseClass & Quot; implémente & "; interface ISomeInterface &"; et " class SomeDerivedClass: SomeBaseClass, ISomeInterface " implémente également l'interface, une méthode statique pour implémenter une méthode d'interface échouerait lors de la compilation car une méthode statique ne peut pas avoir la même signature qu'une méthode d'instance (qui serait présente dans la classe de base pour implémenter l'interface).

Une classe statique est fonctionnellement identique à un singleton et remplit le même objectif qu'un singleton avec une syntaxe plus propre. Puisqu'un singleton peut implémenter une interface, les implémentations d'interface par la statique sont conceptuellement valables.

Donc, cela se résume simplement à la limitation du conflit de noms C #, par exemple, et des méthodes statiques du même nom sur l'ensemble de l'héritage. Il n'y a aucune raison pour que C # ne puisse pas être & Quot; mis à jour & Quot; prendre en charge les contrats de méthodes statiques (interfaces).

ne faites pas cela, essayez plutôt les classes abstraites

public abstract class ExampleBase
{
    /// <summary>
    /// Do it
    /// </summary>
    public virtual abstract static void DoIt();
}

Lorsqu'une classe implémente une interface, elle crée une instance pour les membres de l'interface. Bien qu'un type statique ne possède pas d'instance, il est inutile d'avoir des signatures statiques dans une interface.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top