Search This Blog

Thursday, March 24, 2016

A small self written LINQ as example

I talk a lot about LINQ as it's one of the main feature I love in .NET, but if you are not in the .NET world or even if you are a .NET developer you may not know how this works.

I will not dig into the LINQ / SQL because it is a bit more complex and requires more work, however a simple LINQ of a memory list is quite easy to do implement. That's what I will present here.

As basis let's start with some data and a class to work with it:

using System;
using System.Collections.Generic;
namespace SmallLinq
{
    class MyData    {
        public string Name { getset; }
        public int Score { getset; }
    }
    class Program    {
        static void Main(string[] args)
        {
            var myData = new List<MyData> {
                new MyData { Name = "Roberto", Score = 5 },
                new MyData { Name = "Chantal", Score = 10 },
                new MyData { Name = "Ivan", Score = 2 },
                new MyData { Name = "Ingrid", Score = 4 },
                new MyData { Name = "Florian", Score = 20 },
                new MyData { Name = "Stephan", Score = 1 }
            };
        }
    }
}
Now let's start with a simple "where" filter where we could define what we want to extract. To do so, I would like to make it like LINQ do, an extension function which should work with whatever IEnumerable (or nearly):

using System;
using System.Collections.Generic;
namespace SmallLinq
{
    class MyData    {
        public string Name { getset; }
        public int Score { getset; }
    }
    class Program    {
        static void Main(string[] args)
        {
            var myData = new List<MyData> {
                new MyData { Name = "Roberto", Score = 5 },
                new MyData { Name = "Chantal", Score = 10 },
                new MyData { Name = "Ivan", Score = 2 },
                new MyData { Name = "Ingrid", Score = 4 },
                new MyData { Name = "Florian", Score = 20 },
                new MyData { Name = "Stephan", Score = 1 }
            };
            foreach (var i in myData.MiniWhere(row => row.Name.StartsWith("I")))
            {
                Console.WriteLine(i.Name + " " + i.Score);
            }
            Console.ReadKey();
        }
    }
    public static class MiniLinq    {
        public static IEnumerable<TType> MiniWhere<TType>(this IEnumerable<TType> source, Func<TTypebool> predicate)
        {
            foreach (var i in source)
            {
                if (predicate(i))
                    yield return i;
            }
        }
    }
}
What I added here is the "MiniLinq" static class, with a single static function inside. The <TType> statement let me work with basically any type, and will simply instruct the compiler to return an enumerable of the same type as it got. The first parameter has the keyword "this" and you see in my call in the Main function that it can then be applied to a List class which is not defined by me. This would really work with any enumerable classes. Finally the last parameter of my function is a "predicate" or if you want a callback which should return a Boolean based on a value received, in the call I use a lambda expression but it's nothing else than a function created on the fly for me. The syntax is really small but let you do then whatever filtering you want. In the example it simply shows whoever has the name starting with a capital i.

Let's continue and add a some sorting functions as well:

    class Program    {
...
            foreach (var i in myData.MiniOrderDescending(row => row.Score))
            {
                Console.WriteLine(i.Name + " " + i.Score);
            }
            Console.ReadKey();
        }
    }
    public static class MiniLinq    {
...
        public static IEnumerable<TType> MiniOrder<TTypeTKey>(this IEnumerable<TType> source, Func<TTypeTKey> keySelector) where TKey : IComparable        {
            var list = new List<TType>(source);
            list.Sort((a, b) => keySelector(a).CompareTo(keySelector(b)));
            return list;
        }
        public static IEnumerable<TType> MiniOrderDescending<TTypeTKey>(this IEnumerable<TType> source, Func<TTypeTKey> keySelector) where TKey : IComparable        {
            var list = new List<TType>(source);
            list.Sort((a, b) => keySelector(b).CompareTo(keySelector(a)));
            return list;
        }
    }
To reduce the clutter I removed the parts which are the same (at the end of the post you will have all the code).

The sorting implemented uses a restriction in which the key must be IComparable, other than that it is still left mostly to the end user to choose how to sort.

One part which is really useful with LINQ is the selector which let you transform a source into something else. Let's create is as well:

using System;
using System.Collections.Generic;
namespace SmallLinq
{
    class MyData    {
        public string Name { getset; }
        public int Score { getset; }
    }
    class Program    {
        static void Main(string[] args)
        {
            var myData = new List<MyData> {
                new MyData { Name = "Roberto", Score = 5 },
                new MyData { Name = "Chantal", Score = 10 },
                new MyData { Name = "Ivan", Score = 2 },
                new MyData { Name = "Ingrid", Score = 4 },
                new MyData { Name = "Florian", Score = 20 },
                new MyData { Name = "Stephan", Score = 1 }
            };
            foreach (var i in myData.MiniOrderDescending(row => row.Score).MiniSelect(row=>row.Name))
            {
                Console.WriteLine(i);
            }
            Console.ReadKey();
        }
    }
    public static class MiniLinq    {
        public static IEnumerable<TType> MiniWhere<TType>(this IEnumerable<TType> source, Func<TTypebool> predicate)
        {
            foreach (var i in source)
            {
                if (predicate(i))
                    yield return i;
            }
        }
        public static IEnumerable<TType> MiniOrder<TTypeTKey>(this IEnumerable<TType> source, Func<TTypeTKey> keySelector) where TKey : IComparable        {
            var list = new List<TType>(source);
            list.Sort((a, b) => keySelector(a).CompareTo(keySelector(b)));
            return list;
        }
        public static IEnumerable<TType> MiniOrderDescending<TTypeTKey>(this IEnumerable<TType> source, Func<TTypeTKey> keySelector) where TKey : IComparable        {
            var list = new List<TType>(source);
            list.Sort((a, b) => keySelector(b).CompareTo(keySelector(a)));
            return list;
        }
        public static IEnumerable<TResult> MiniSelect<TTypeTResult>(this IEnumerable<TType> source, Func<TTypeTResult> selector)
        {
            foreach (var i in source)
            {
                yield return selector(i);
            }
        }
    }
}

The selected is surprising simple to implement as you can see. This time I gave the whole source such that you can check it by yourself.
I hope this small tour of how LINQ could be implemented opens the door to more tricks in your code, and actually make things clearer in your mind and is not meant to replace what .NET already offers.

No comments:

Post a Comment