LINQ-to-groups

weicco 06.08.08 11:50

Mahdollistaa LINQ kyselyssä haettujen ryhmien "helpon" läpikäynnin.

 Tekstiversio  Arvo: 3 (7 ääntä)  Äänestä: +  -
namespace Weicco.Linq
{
        /// <summary>
        /// Rajapinta, jolla voidaan käydä LINQ kyselystä saadut ryhmät yksitellen läpi.
        /// </summary>
        /// <typeparam name="TKey">Avaimen tyyppi.</typeparam>
        /// <typeparam name="TElement">Elementin tyyppi.</typeparam>
        /// <example>
        /// <code>
        /// var query = from c in "123451234123121".ToCharArray()
        ///          group c by c;
        ///          
        /// GroupCollection<char, char>.Create(query).Each(delegate(IGroup<char, char> group)
        /// {
        ///          char groupKey = group.Key;
        /// });
        /// </code>
        /// </example>
        public interface IGroupCollection<TKey, TElement> : IEnumerable<IGroup<TKey, TElement>>
        {
                void Each(GroupDelegate<TKey, TElement> fn);
        }

        /// <summary>
        /// Luokka, joka toteuttaa LINQ kyselystä saatujen ryhmien enumeroimisen.
        /// </summary>
        public class GroupCollection<TKey, TElement> : IGroupCollection<TKey, TElement>
        {
                /// <summary>
                /// Konstruktori. Tallettaa enumerablen jotta sitä voidaan käyttää myöhemmin.
                /// </summary>
                /// <param name="enumerable">LINQ kyselystä saatu IEnumerablen toteuttava objekti.</param>
                private GroupCollection(IEnumerable<IGrouping<TKey, TElement>> enumerable)
                {
                        _Enumerable = enumerable;
                }

                IEnumerable<IGrouping<TKey, TElement>> _Enumerable;

                /// <summary>
                /// Staattinen luontimetodi, jotta saamme koodin kauniimmaksi.
                /// </summary>
                /// <param name="enumerable">LINQ kyselystä saatu IEnumerablen toteuttava objekti.</param>
                /// <returns>Palauttaa uuden GroupCollection objektin.</returns>
                public static IGroupCollection<TKey, TElement> Create(IEnumerable<IGrouping<TKey, TElement>> enumerable)
                {
                        return new GroupCollection<TKey, TElement>(enumerable);
                }

                /// <summary>
                /// Käy läpi jokaisen ryhmän (group) ja kutsuu käyttäjän määrittämää delegaattia.
                /// </summary>
                /// <param name="fn">Delegaatti.</param>
                void IGroupCollection<TKey, TElement>.Each(GroupDelegate<TKey, TElement> fn)
                {
                        foreach (IGroup<TKey, TElement> group in this)
                        {
                                fn(group);
                        }
                }

                /// <summary>
                /// Enumeraattori, jolla käydään kaikki ryhmät (group) läpi LINQ kyselystä.
                /// </summary>
                class GroupCollectionEnumerator : IEnumerator<IGroup<TKey, TElement>>
                {
                        /// <summary>
                        /// Konstruktori.
                        /// </summary>
                        /// <param name="enumerator">LINQ kyselystä saatu enumeraattori.</param>
                        internal GroupCollectionEnumerator(IEnumerator<IGrouping<TKey, TElement>> enumerator)
                        {
                                _Enumerator = enumerator;
                        }

                        IEnumerator<IGrouping<TKey, TElement>> _Enumerator;

                        /// <summary>
                        /// Palauttaa nykyisen ryhmän (group).
                        /// </summary>
                        IGroup<TKey, TElement> IEnumerator<IGroup<TKey, TElement>>.Current
                        {
                                get { return new Group<TKey, TElement>(_Enumerator.Current); }
                        }

                        /// <summary>
                        /// Täällä ei tapahdu paljoa.
                        /// </summary>
                        void IDisposable.Dispose()
                        {

                        }

                        /// <summary>
                        /// Palauttaa nykyisen ryhmän (group).
                        /// </summary>
                        /// <remarks>
                        /// Vanha ei-generic property, jota tuskin on tarvis käyttää enää koskaan.
                        /// </remarks>
                        object IEnumerator.Current
                        {
                                get { return new Group<TKey, TElement>(_Enumerator.Current); }
                        }

                        /// <summary>
                        /// Siirtyy seuraavaan ryhmään (group) listassa. Palauttaa True jos siirtyminen
                        /// onnistui, False jos lista on käyty loppuun asti.
                        /// </summary>
                        /// <returns></returns>
                        bool IEnumerator.MoveNext()
                        {
                                return _Enumerator.MoveNext();
                        }

                        /// <summary>
                        /// Siirtää enumeraattorin listan alkuun.
                        /// </summary>
                        void IEnumerator.Reset()
                        {
                                _Enumerator.Reset();
                        }
                }

                /// <summary>
                /// Palauttaa uuden ryhmäenumeraattorin.
                /// </summary>
                /// <returns>Uusi ryhmäenumeraattorin.</returns>
                IEnumerator<IGroup<TKey, TElement>> IEnumerable<IGroup<TKey, TElement>>.GetEnumerator()
                {
                        return new GroupCollectionEnumerator(_Enumerable.GetEnumerator());
                }

                /// <summary>
                /// Palauttaa uuden ryhmäenumeraattorin.
                /// </summary>
                /// <returns>Uusi ryhmäenumeraattorin.</returns>
                /// <remarks>
                /// Typerä System.Collections nimiavaruus!
                /// </remarks>
                IEnumerator IEnumerable.GetEnumerator()
                {
                        return new GroupCollectionEnumerator(_Enumerable.GetEnumerator());
                }
        }

        /// <summary>
        /// Delegaatti jonka avulla voidaan käydä käydä ryhmät.
        /// </summary>
        /// <typeparam name="TKey">Avaimen tyyppi.</typeparam>
        /// <typeparam name="TElement">Elementin tyyppi.</typeparam>
        /// <param name="group">Tähän tulee referenssi yksittäisen ryhmän lukemisen
        /// mahdollistavaan rajapintaa.</param>
        public delegate void GroupDelegate<TKey, TElement>(IGroup<TKey, TElement> group);

        /// <summary>
        /// Rajapinta yksittäisen ryhmän lukemiseen.
        /// </summary>
        /// <typeparam name="TKey">Avaimen tyyppi.</typeparam>
        /// <typeparam name="TElement">Elementin tyyppi.</typeparam>
        /// <example>
        /// <code>
        /// var query = from c in "123451234123121".ToCharArray()
        /// group c by c;
        ///
        /// GroupCollection<char, char>.Create(query).Each(delegate(IGroup<char, char> group)
        /// {
        ///          char groupKey = group.Key;
        ///
        ///          group.Each(delegate(char item)
        ///          {
        ///               char groupItem = item;
        ///          });
        /// });
        /// </code>
        /// </example>
        public interface IGroup<TKey, TElement> : IEnumerable<TElement>
        {
                TKey Key { get; }

                void Each(GroupItemDelegate<TElement> item);
        }

        /// <summary>
        /// Luokka, joka toteuttaa ryhmän lukemisen.
        /// </summary>
        /// <typeparam name="TKey">Avaimen tyyppi.</typeparam>
        /// <typeparam name="TElement">Elementin tyyppi.</typeparam>
        class Group<TKey, TElement> : IGroup<TKey, TElement>
        {
                /// <summary>
                /// Konstruktori.
                /// </summary>
                internal Group(IGrouping<TKey, TElement> grouping)
                {
                        _Grouping = grouping;
                }

                IGrouping<TKey, TElement> _Grouping;

                /// <summary>
                /// Toteuttaa ryhmän läpikäynnin ja kutsuu käyttäjän määrittelemää delegaattia.
                /// </summary>
                /// <param name="fn">Käyttäjän määrittelemä delegaatti.</param>
                void IGroup<TKey, TElement>.Each(GroupItemDelegate<TElement> fn)
                {
                        foreach (TElement item in this)
                        {
                                fn(item);
                        }
                }

                /// <summary>
                /// Palauttaa ryhmän avaimen.
                /// </summary>
                TKey IGroup<TKey, TElement>.Key
                {
                        get
                        {
                                return _Grouping.Key;
                        }
                }

                /// <summary>
                /// Luokka, joka toteuttaa ryhmän jäsenien enumeroinnin.
                /// </summary>
                class GroupItemEnumerator : IEnumerator<TElement>
                {
                        /// <summary>
                        /// Konstrutori.
                        /// </summary>
                        public GroupItemEnumerator(IEnumerator<TElement> enumerator)
                        {
                                _Enumerator = enumerator;
                        }

                        IEnumerator<TElement> _Enumerator;

                        /// <summary>
                        /// Palauttaa nykyisen ryhmän jäsenen.
                        /// </summary>
                        public TElement Current
                        {
                                get { return _Enumerator.Current; }
                        }

                        /// <summary>
                        /// Ei mitään tehtävää.
                        /// </summary>
                        public void Dispose()
                        {

                        }

                        /// <summary>
                        /// Palauttaa nykyisen ryhmän jäsenen.
                        /// </summary>
                        object IEnumerator.Current
                        {
                                get { return _Enumerator.Current; }
                        }

                        /// <summary>
                        /// Siirtyy seuraavaan jäseneen ryhmässä. Palauttaa True jos siirtyminen
                        /// onnistui, False jos lista on käyty loppuun asti.
                        /// </summary>
                        /// <returns></returns>
                        public bool MoveNext()
                        {
                                return _Enumerator.MoveNext();
                        }

                        /// <summary>
                        /// Palaa ryhmän alkuun.
                        /// </summary>
                        public void Reset()
                        {
                                _Enumerator.Reset();
                        }
                }

                /// <summary>
                /// Palauttaa enumeraattorin, jolla voidaan lukea ryhmän jäsenet.
                /// </summary>
                /// <returns>Enumeraattori.</returns>
                public IEnumerator<TElement> GetEnumerator()
                {
                        return new GroupItemEnumerator(_Grouping.GetEnumerator());
                }

                /// <summary>
                /// Palauttaa enumeraattorin, jolla voidaan lukea ryhmän jäsenet.
                /// </summary>
                /// <returns>Enumeraattori.</returns>
                IEnumerator IEnumerable.GetEnumerator()
                {
                        return new GroupItemEnumerator(_Grouping.GetEnumerator());
                }
        }

        /// <summary>
        /// Delegaatti, jota kutsutaan jokaiselle ryhmän jäsenelle.
        /// </summary>
        /// <typeparam name="TElement">Jäsenen tyyppi.</typeparam>
        /// <param name="item">Jäsen.</param>
        public delegate void GroupItemDelegate<TElement>(TElement item);
}

weicco 11:57 6.8.08 
Tuolla seassa on käyttöesmerkkejä pari kappaletta, mutta pistetään tähän vielä koko touhu:


C#
using System;
using System.Collections;
using System.Collections.Generic;
using System.Windows.Forms;
using System.Linq;
using Weicco.Linq;

namespace WindowsFormsApplication2
{
        public partial class Form1 : Form
        {
                public Form1()
                {
                        InitializeComponent();
                }

                private void Form1_Load(object sender, EventArgs e)
                {
                        ListView listView = new ListView
                        {
                                Dock = DockStyle.Fill,
                                ShowGroups = true,
                                View = View.Details
                        };
                       
                        listView.Columns.Add("Numero");

                        var query = from c in "123451234123121".ToCharArray()
                                                group c by c;

                        GroupCollection<char, char>.Create(query).Each(delegate(IGroup<char, char> group)
                        {
                                ListViewGroup listViewGroup = new ListViewGroup
                                {
                                        Header = group.Key.ToString(),
                                        Name = group.Key.ToString()
                                };

                                listView.Groups.Add(listViewGroup);

                                group.Each(delegate(char item)
                                {
                                        ListViewItem listViewItem = new ListViewItem
                                        {
                                                Group = listViewGroup,
                                                Text = item.ToString()
                                        };

                                        listView.Items.Add(listViewItem);
                                });
                        });

                        Controls.Add(listView);
                }
        }
}
 
weicco 17:23 27.8.08 
Piru. Yritin tehdä Extension Methodin IEnumerable<IGrouping<TKey, TElement>> interfacelle, mutta se ei jostain syystä onnistunut. Jos onnistuisi, koko touhun saisi kutistettua ihan muutamaan riviin.
weicco 10:03 9.9.08 
Monologi jatkuu. Huomasin tämän erittäin käytännölliseksi, kun piti lukea tietokannasta tapahtumia ja järjestellä ne päivämäärän mukaan käyttäen Expanderia. Tuuppasin sivulle StackPanelin, johon tein pvm:n mukaan Expandereita ja Expanderin sisällöksi puuppasin tapahtumia Labelin avulla. Toimii kuin junan vessa ja koodikin on nättiä :)
editoitu: 19:28 9.10.08
remix 15:07 9.10.08 
Rupesin tässä itsekin tutustumaan C#:n uusiin ominaisuuksin ja varsinkin LINQ:iin pienen .NET-tauon jälkeen, joten voi olla, että minulla ei ole vielä kaikki asiat selvinä. Tästä on nyt kuitenkin pakko kysyä, että mitä etua tällä rajapinnalla saadaan verrattuna sisäkkäisiin silmukoihin? Nuo pitkät delegaattien määrittelyt näyttävät aika vaikeilta verrattuna pariin foreachiin, millä ainakin tässä esimerkissä pärjää mielestäni ihan hyvin.

EDIT: No joo. Ehkä delegaattien käytölle löytyy tässäkin tapauksessa syitä, kun ajattelee vähän laajemmin. Ajatukset pyöri vaan nyt tuon LINQ:n ympärillä. Eli vaikka itse ryhmien läpikäynti onnistuu silmukoilla, voipi olla tilanteita, joissa haluaa antaa varsinaisen datan käyttöön jonnekin muualle. Esim. formille, niinkuin tuossa Expander esimerkissäsi. Tai jotain...