web-dev-qa-db-fra.com

Pourquoi cette interface doit-elle être explicitement implémentée?

Revenant à C # après quelques années, je suis un peu rouillé. Je suis tombé sur ce code (simplifié) et ça me gratte la tête.

Pourquoi devez-vous implémenter explicitement la propriété IDataItem.Children? La propriété normale Children ne satisfait-elle pas à l'exigence? Après tout, la propriété est utilisée directement pour la satisfaire. Pourquoi n'est-ce pas implicite?

public interface IDataItem {

    IEnumerable<string> Children { get; }
}

public class DataItem : IDataItem {

    public Collection<string> Children { get; } = new Collection<string>();

    // Why doesn't 'Children' above implement this automatically?!
    // After all it's used directly to satisfy the requirement!
    IEnumerable<string> IDataItem.Children => Children;
}

Selon la source C #, voici la définition de Collection<T>:

[System.Runtime.InteropServices.ComVisible(false)]
public class Collection<T> :
    System.Collections.Generic.ICollection<T>,
    System.Collections.Generic.IEnumerable<T>, <-- Right Here
    System.Collections.Generic.IList<T>,
    System.Collections.Generic.IReadOnlyCollection<T>,
    System.Collections.Generic.IReadOnlyList<T>,
    System.Collections.IList

Comme vous pouvez le voir, il implémente explicitement IEnumerable<T> et si je comprends bien, si 'X' implémente 'Y', alors 'X' est un 'Y', donc Collection<String> est un IEnumerable<String> et je ne l'es Bien sûr, pourquoi il n'est pas satisfait.

12
MarqueIV

Peut-être que cet exemple le rend plus clair. Nous voulons que les signatures correspondent à exactement1,2, aucune substitution n'est autorisée, malgré les relations d'héritage entre les types.

Nous ne sommes pas autorisés à écrire ceci:

public interface IDataItem {

    void DoStuff(string value);
}

public class DataItem : IDataItem {

    public void DoStuff(object value) { }
}

Votre exemple est le même, à l'exception du fait que vous demandez des types de retour plutôt que des paramètres (et que vous utilisez une conversion restreinte plutôt qu'élargie, pour des raisons évidentes). Néanmoins, le même principe s'applique. Quand il s'agit de faire correspondre des signatures, les types doivent correspondre à exactement3.

Vous pouvez demander une langue qui permettrait que de telles choses se produisent et de telles langues peuvent exister. Mais le fait est que ce sont les règles de C #.


1En dehors d'un support limité pour Co- et Contra-variance impliquant génériques et interfaces/délégués.

2Certains pourraient se demander si la signature est le bon mot à utiliser ici, car dans ce cas, les types de retour importent autant que les types de paramètre, l'arity générique, etc. Dans la plupart des autres situations où quelqu'un parle de signatures de méthode C #, il ignorera explicitement le type de retour car il considère (explicitement ou implicitement) ce que disent les règles overloading. de la signature.

Néanmoins, je suis satisfait de mon utilisation du mot "signature" ici. Les signatures ne sont pas formellement définies dans la spécification C # et, là où elles sont utilisées, il est souvent utile d'indiquer quelles parties de la signature ne sont pas à prendre en compte pour la surcharge.

3Sans parler des problèmes qui seraient soulevés si votre méthode Children renvoyait une struct qui implémentait IEnumerable<string>. Vous avez maintenant une méthode qui renvoie la valeur d'un type de valeur et un appelant (via l'interface IDataItem qui s'attend à recevoir un reference à un objet.

Donc, ce n'est même pas que la méthode pourrait être utilisée telle quelle. Nous aurions (dans ce cas) des conversions de boxe hidden pour implémenter l'interface. Quand cette partie de C # a été spécifiée, ils ont, je crois, essayé de ne pas avoir trop de "magie cachée" pour le code que vous pourriez facilement écrire vous-même.

6

Dans votre exemple, votre propriété Children "normale" ne satisfait pas réellement l'exigence d'interface. Le type est différent. Peu importe que vous puissiez le lancer, ils sont différents.

Un exemple similaire, peut-être un peu plus évident, est de mettre en œuvre une interface avec une méthode réelle qui renvoie IEnumerable et d’essayer une méthode ICollection à partir de la classe réelle. Il y a toujours cette erreur de temps de compilation.

Comme @Ben Voigt a déclaré que la conversion génère encore du code, et si vous voulez l'avoir, vous devez l'ajouter implicitement.

5
JleruOHeP

La question a été répondue ici (vous devez faire correspondre les types d'interface) et nous pouvons illustrer le problème à l'aide d'un exemple. Si cela a fonctionné:

public interface IDataItem {

    IEnumerable<string> Children { get; set; }

    void Wipe();
}

public class DataItem : IDataItem {

    public Collection<string> Children { get; } = new Collection<string>(); 

    public void Wipe() {
        Children.ClearItems();  //Property exists on Collection, but not on IEnumerable
    }
}

Et nous utilisons ensuite ce code comme ceci:

IDataItem x = new DataItem();

//Should be legal, as Queue implements IEnumerable. Note entirely sure
//how this would work, but this is the system you are asking about.
x.Children = new Queue<string>();

x.Wipe();  // Will fail, as Queue does not have a ClearItems method within

Soit vous voulez que la propriété soit uniquement énumérable, soit vous avez besoin des propriétés de la classe Collection - tapez votre interface de manière appropriée.

0
Paddy