Question

Mise à jour : Je vous remercie tous les commentaires qui ont essentiellement compris l'opposition unanime. Bien que chaque objection était valable, je pense que le clou final dans le cercueil était l'observation astucieuse d'Ani qui, en fin de compte, même le un miniscule avantages que cette idée apparemment offerte - l'élimination des boilerplate code -. a été niée par le fait que l'idée elle-même nécessiterait son propre code boilerplate

Alors oui, pensez à moi convaincu:. Ce serait une mauvaise idée

Et juste pour genre de sauvetage ma dignité un peu: je pourrais avoir joué pour l'amour de la discussion, mais on ne m'a jamais vraiment vendu sur cette idée pour commencer - simplement curieux d'entendre ce que les autres avaient à dire à ce sujet. Honnête.


Avant de rejeter cette question aussi absurde, je vous demande de considérer ce qui suit:

  1. hérite IEnumerable<T> de * IEnumerable, ce qui signifie que tout type qui implémente IEnumerable<T> doit généralement mettre en œuvre deux IEnumerable<T>.GetEnumerator et (explicitement) IEnumerable.GetEnumerator. Cela revient essentiellement au code passe-partout.
  2. Vous pouvez foreach sur tout type qui a une méthode GetEnumerator, aussi longtemps que cette méthode retourne un objet d'un certain type avec une méthode MoveNext et une propriété Current. Donc, si votre définit de type un méthode avec la public IEnumerator<T> GetEnumerator() signature, il est légal d'énumérer dessus en utilisant foreach.
  3. De toute évidence, il y a beaucoup de code là-bas qui nécessite l'interface IEnumerable<T> - par exemple, essentiellement toutes les méthodes d'extension LINQ. Heureusement, pour passer d'un type que vous pouvez foreach à un IEnumerable<T> est trivial en utilisant la génération de iterator automatique qui fournit C # via le mot-clé yield.

Alors, mettre tout cela ensemble, j'ai eu cette idée folle: si je définis juste ma propre interface qui ressemble à ceci:

public interface IForEachable<T>
{
    IEnumerator<T> GetEnumerator();
}

Ensuite chaque fois que je définir un type que je veux être dénombrable, je mets en œuvre cette interface au lieu de IEnumerable<T>, ce qui élimine la nécessité de mettre en œuvre deux méthodes de GetEnumerator (un explicite) . Par exemple:

class NaturalNumbers : IForEachable<int>
{
   public IEnumerator<int> GetEnumerator()
   {
       int i = 1;
       while (i < int.MaxValue)
       {
           yield return (i++);
       }
   }

   // Notice how I don't have to define a method like
   // IEnumerator IEnumerable.GetEnumerator().
}

Enfin , afin de rendre ce type compatible avec le code que Finalité attendre l'interface IEnumerable<T>, je peux définir une méthode d'extension pour passer de toute IForEachable<T> à un IEnumerable<T> comme ceci:

public static class ForEachableExtensions
{
    public static IEnumerable<T> AsEnumerable<T>(this IForEachable<T> source)
    {
        foreach (T item in source)
        {
            yield return item;
        }
    }
}

Il me semble que faire cela me permet de concevoir des types qui sont utilisables dans tous les sens comme des implémentations de IEnumerable<T>, mais sans que la mise en œuvre de satanés IEnumerable.GetEnumerator explicite dans chacun.

Par exemple:

var numbers = new NaturalNumbers();

// I can foreach myself...
foreach (int x in numbers)
{
    if (x > 100)
        break;

    if (x % 2 != 0)
        continue;

    Console.WriteLine(x);
}

// Or I can treat this object as an IEnumerable<T> implementation
// if I want to...
var evenNumbers = from x in numbers.AsEnumerable()
                  where x % 2 == 0
                  select x;

foreach (int x in evenNumbers.TakeWhile(i => i <= 100))
{
    Console.WriteLine(x);
}

Qu'est-ce que les gars vous pensez de cette idée? Suis-je manque une raison pour laquelle ce serait une erreur?

Je me rends compte qu'il semble probablement comme une solution trop complexe pour ce qui est pas un gros problème pour commencer (je doute tout le monde vraiment soins que beaucoup d'avoir à définir explicitement l'interface IEnumerable) ; mais juste sauté dans ma tête et je ne vois aucun problème évident que cette approche pose.

En général, si je peux écrire une quantité modérée de code une fois pour me sauver la peine d'avoir à écrire une petite quantité de code beaucoup de fois , me , ça vaut le coup.

* Est-ce la bonne terminologie à utiliser? Je suis toujours hésité à dire une interface « hérite de » l'autre, comme cela ne semble pas saisir correctement la relation entre eux. Mais peut-être qu'il est juste sur.

Était-ce utile?

La solution

N'êtes-vous pas le mouvement juste passe-partout ailleurs - d'écrire le IEnumerable.GetEnumeratormethod sur chaque classe pour appeler votre extension AsEnumerable chaque fois qu'un IEnumerable<T> est prévu? En règle générale, je pense un type dénombrable à utiliser pour effectuer des requêtes fois beaucoup plus qu'il est écrit (ce qui est exactement une fois). Cela signifie que ce modèle conduira à plus boilerplate, en moyenne.

Autres conseils

Vous manque une chose énorme -

Si vous implémentez votre propre interface au lieu de IEnumerable<T>, votre classe ne fonctionnera pas avec les méthodes cadres attendent IEnumerable<T> - surtout, vous serez totalement incapable d'utiliser LINQ, ou utilisez votre classe pour construire un List<T>, ou bien d'autres abstractions utiles .

Vous pouvez accomplir cela, comme vous le mentionnez, via une méthode d'extension séparée - cependant, cela a un coût. En utilisant une méthode d'extension pour convertir à un IEnumerable<T>, vous ajoutez un autre niveau d'abstraction nécessaire pour utiliser votre classe (que vous allez faire beaucoup plus souvent que authoring la classe), et vous réduisez la performance (méthode de vulgarisation , en effet, générer une nouvelle implémentation de la classe interne, ce qui est vraiment inutile). Plus important encore, tout autre utilisateur de votre classe (ou plus tard) devra apprendre une nouvelle API qui ne mène à rien - vous rendre plus difficile votre classe à utiliser par ne pas utiliser les interfaces standard, car elle viole les attentes de l'utilisateur

Vous avez raison. It Finalité semble une solution trop complexe à un problème assez facile

Il introduit également un niveau d'indirection supplémentaire pour chaque étape de l'itération. Probablement pas un problème de performance, mais encore un peu inutile quand je ne pense pas que vous êtes gagnant vraiment quelque chose de très important.

En outre, bien que votre méthode d'extension vous permet de convertir tout IForEachable<T> en un IEnumerable<T>, cela signifie que votre type lui-même pas satisfaire une contrainte générique comme ceci:

public void Foo<T>(T collection) where T : IEnumerable

ou similaires. En fait, en ayant pour effectuer votre conversion, que vous perdez la capacité de traiter un seul objet comme à la fois une implémentation de IEnumerable<T> et le type réel de béton.

En outre, de ne pas mettre en œuvre IEnumerable, vous vous compter sur des initialisations de collecte. (Parfois je mets en œuvre IEnumerable explicitement juste opt dans l'initialisation de la collecte, jetant une exception GetEnumerator().)

Oh, et vous avez également mis en place un peu d'infrastructure supplémentaire qui ne connaît pas à tout le monde dans le monde, par rapport aux vastes hordes de développeurs C # qui connaissent déjà IEnumerable<T>.

Dans notre base de code, il y a probablement plus de 100 cas de cet extrait exact:

IEnumerator IEnumerable.GetEnumerator()
{
    return this.GetEnumerator();
}

Je suis vraiment OK avec ça. Il est un petit prix à payer pour une compatibilité totale avec tous les morceau de code .NET jamais écrit;)

Votre solution proposée nécessite essentiellement chaque consommateur / appelant de votre nouvelle interface de se rappeler également appeler une méthode d'extension spéciale sur elle avant qu'il ne soit utile .

Il est beaucoup plus facile à utiliser IEnumerable pour la réflexion. Essayer d'invoquer des interfaces génériques par réflexion est une douleur telle. La peine de la boxe par IEnumerable est perdue par la tête elle-même réflexion, alors pourquoi vous ne devez utiliser une interface générique? Pour ce qui est un exemple, sérialisation vient à l'esprit.

Je pense que tout le monde a déjà mentionné les raisons techniques de ne pas le faire, alors je vais ajouter ceci dans le mélange: nécessitant votre utilisateur d'appeler AsEnumerable () sur votre collection pour pouvoir utiliser des extensions dénombrables serait contraire au principe de moins surprise.

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