1 votes

Est-il courant (ou encouragé) de surcharger une fonction pour accepter IEnumerable<T>, ICollection<T>, IList<T>, etc.?

MODIFIER:

Des réponses données, il m'est plutôt clair comment la conception que je demande ci-dessous devrait en fait être implémentée. Avec ces suggestions à l'esprit (et en réponse à un commentaire indiquant poliment que mon code exemple ne compile même pas), j'ai modifié le code suivant pour refléter ce que semble être le consensus général. La question qui reste peut ne plus avoir de sens à la lumière du code, mais je la laisse telle quelle pour la postérité.


Supposons que j'ai trois surcharges d'une fonction, une prenant IEnumerable, une prenant ICollection, et une prenant IList, quelque chose comme ce qui suit:

public static T GetMiddle(IEnumerable values) {
    IList list = values as IList;
    if (list != null) return GetMiddle(list);

    int count = GetCount(values);

    T middle = default(T);
    int index = 0;

    foreach (T value in values) {
        if (index++ >= count / 2) {
            middle = value;
            break;
        }
    }

    return middle;
}

private static T GetMiddle(IList values) {
    int middleIndex = values.Count / 2;
    return values[middleIndex];
}

private static int GetCount(IEnumerable values) {
    // si les valeurs sont en fait une ICollection (par exemple, List),
    // nous pouvons obtenir le nombre assez bon marché
    ICollection genericCollection = values as ICollection;
    if (genericCollection != null) return genericCollection.Count;

    // même pour ICollection (par exemple, Queue, Stack)
    ICollection collection = values as ICollection;
    if (collection != null) return collection.Count;

    // sinon, nous devons compter les valeurs nous-mêmes
    int count = 0;
    foreach (T value in values) count++;

    return count;
}

L'idée ici est que, si j'ai un IList, cela facilite mon travail; d'autre part, je peux toujours faire le travail avec un ICollection ou même un IEnumerable; l'implémentation pour ces interfaces n'est tout simplement pas aussi efficace.

Je n'étais pas sûr que cela fonctionnerait même (si le runtime serait capable de choisir une surcharge basée sur le paramètre passé), mais je l'ai testé et il semble que oui.

Ma question est : y a-t-il un problème avec cette approche auquel je n'ai pas pensé ? Alternativement, cette approche est-elle en fait bonne, mais y a-t-il une meilleure façon de l'accomplir (peut-être en essayant de caster l'argument values en un IList d'abord et en exécutant la surcharge plus efficace si le cast fonctionne) ? Je suis juste intéressé à savoir ce que les autres pensent.

5voto

Trillian Points 2922

Si vous jetez un coup d'œil à la façon dont les méthodes d'extension LINQ sont implémentées à l'aide de Reflector, vous verrez que quelques méthodes d'extension sur IEnumerable, telles que Count(), tentent de convertir la séquence en ICollection ou en IList pour optimiser l'opération (par exemple, en utilisant la propriété Count de ICollection au lieu de parcourir un IEnumerable et de compter les éléments). Votre meilleure option est donc très probablement d'accepter un IEnumerable et ensuite de faire ce genre d'optimisations si ICollection ou IList sont disponibles.

2voto

Wayne Points 3098

Je pense qu'une version acceptant IEnumerable serait la voie à suivre, et vérifier à l'intérieur de la méthode si le paramètre est l'un des types de collection les plus dérivés. Avec trois versions comme vous le proposez, vous perdez l'avantage d'efficacité si quelqu'un vous transmet un (runtime) IList que le compilateur considère statiquement comme IEnumerable:

        IList stringList = new List { "A", "B", "C" };
        IEnumerable seq = stringList;
        Extensions.GetMiddle(stringList); // appelle la version IList
        Extensions.GetMiddle(seq);        // appelle la version IEnumerable

1voto

Joe Points 60749

Je dirais que c'est inhabituel et potentiellement confus, donc ce ne serait probablement pas un bon choix pour une API publique.

Vous pourriez accepter un paramètre IEnumerable et vérifier internement s'il s'agit en fait d'une ICollection ou d'une IList, et optimiser en conséquence.

Cela pourrait être analogue à certaines des optimisations dans quelques-unes des méthodes d'extension IEnumerable dans le framework .NET 3.5.

1voto

Brian Gideon Points 26683

Je suis vraiment indifférent. Si je voyais les choses comme vous, je n'en penserais rien. Mais l'idée de Joe a du mérite. Ça pourrait ressembler à ce qui suit.

public static T GetMiddle(IEnumerable values)
{
  if (values is IList) return GetMiddle((IList)values);
  if (values is ICollection) return GetMiddle((ICollection)values);

  // Utilisez l'implémentation par défaut ici.
}

private static T GetMiddle(ICollection values)
{
}

private static T GetMiddle(IList values)
{
}

1voto

supercat Points 25534

Alors qu'il est légal de surcharger une méthode pour accepter soit un type de base soit un type dérivé, avec tous les autres paramètres étant par ailleurs identiques, il n'est avantageux de le faire que si le compilateur pourra souvent identifier la dernière forme comme étant un meilleur correspondant. Parce qu'il serait très courant que des objets qui implémentent ICollection soient transmis par du code qui a seulement besoin d'un IEnumerable, il serait très courant que les implémentations de ICollection soient transmises à la surcharge de IEnumerable. Par conséquent, la surcharge de IEnumerable devrait probablement vérifier si un objet transmis implémente ICollection et le gérer alors spécialement si c'est le cas.

Si la manière la plus naturelle d'implémenter la logique pour un ICollection serait d'écrire une méthode spéciale pour cela, il n'y aurait rien de particulièrement mal à avoir une surcharge publique qui accepte un ICollection, et avoir la surcharge de IEnumerable appeler la surcharge de ICollection si un objet qui implémente ICollection lui est donné. Avoir une telle surcharge publique n'ajouterait pas beaucoup de valeur, mais elle ne nuirait probablement pas non plus. En revanche, dans des situations où un objet implémente à la fois IEnumerable et ICollection, mais pas ICollection (par exemple, une List implémente IEnumerable et ICollection, mais pas ICollection), on pourrait vouloir utiliser les deux interfaces, mais cela ne pourrait pas être fait sans faire un transtypage dans la méthode qui les utilise, ou transmettre à la méthode qui les utilise à la fois une référence ICollection et une référence IEnumerable. Cette dernière serait très laide dans une méthode publique, et l'approche précédente perdrait les avantages de la surcharge.

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