Click here to Skip to main content
15,884,388 members
Articles / General Programming
Tip/Trick

LINQ as Declarative Programming

Rate me:
Please Sign up or sign in to vote.
4.11/5 (9 votes)
29 Aug 2013CPOL2 min read 25.9K   13   8
How to take full advantage of declarative paradigm by using LINQ.

Introduction

The tip is aimed to show how to utilize the potential of LINQ. It also helps to understand the difference between imperative and declarative programming approaches.

Background

Assume that you're familiarized with the basics of OOP, Imperative Programming Paradigm, Declarative Programming Paradigm, C#, LINQ, Extension Methods, Expression Trees.

Issue

Consider the simple task:

Given the integers {1, ..., 1000}. Find numbers that are divisible by 29.

Let's be up to date and solve that by using the following LINQ query syntax:

C#
from i in Enumerable.Range(1, 1000)
where i % 29 == 0
select i;  

Wow, that sounds almost like the original question! It is very readable and so declarative, isn't it? However, more experienced developer will probably automatically translate that declaration into its imperative counterpart:

C#
foreach (var i in Enumerable.Range(1, 1000)
    if (i % 29 == 0)
        yield return i; 

The truth is that the default implementation of LINQ operators, acts in such way. While the programmer is analysing his translated imperative version of the query he points out, that the implementation is inefficient. The better solution will be as follows:

C#
for (var i = 29; i <= 1000; i += 29)
    yield return i;  

What does he do next, for the sake of the performance? He rids off the LINQ query, writes a custom, imperative, illegible implementation and... that is the pattern, that this tip attempts to change!

Concept

To make a change, we need 3 .NET tools:

  • Extension Methods
  • Method Overloading
  • Expression Trees

LINQ beyond query syntax also has an equivalent method syntax. We might prescribe our original query to the following form:

C#
Enumerable.Range(1, 1000).Where(i => i % 29 == 0); 

It is not as beautiful as the former, but is only aimed to facilitate further considerations. Notice that Where statement is nothing more than method invocation. An extension method Enumerable.Where is more specific. We are free to overload that method by a more concrete version that will capture our invocation:

C#
static IEnumerable<int> Where(this IEnumerable<int> source, Func<int, bool> predicate) 

We could put the boosted algorithm here:

C#
static IEnumerable<int> Where(this IEnumerable<int> source, Func<int, bool> predicate)
{  
    for (var i = 29; i <= 1000; i += 29)
        yield return i;
} 

But that is a one off, inflexible solution. We have ignored the predicate. Fortunately, there is a solution - Expression Trees. Look at the declaration below:

C#
static IEnumerable<int> Where(
    this IEnumerable<int> source,
    Expression<Func<int, bool>> predicate) 

That also works well as the previous one, but gives additional advantages. It allows to explore the internals of given function. Probably the most shocking thing is that the function may never be invoked! Below is the simplified implementation of our custom Where method:

C#
static IEnumerable<int> Where(
    this IEnumerable<int> source,
    Expression<Func<int, bool>> predicate)
{
    var body = predicate.Body as BinaryExpression;
    var bodyLeft = body.Left as BinaryExpression;
    var moduloRight = bodyLeft.Right as ConstantExpression;
    var rightValue = (int) moduloRight.Value;
 
    for (var i = rightValue; i < source.Count(); i += rightValue)
        yield return i;
}    

If something went wrong, then use standard Where method:

C#
if (bodyLeft.NodeType != ExpressionType.Modulo)
    return Enumerable.Where(source, predicate.Compile());  

That's it! Now, we can use our original LINQ query syntax to solve the issue in a readable and also efficient way.

Declarative vs Imperative

Notice that the expression i % 29 == 0 have never been invoked! In imperative programming, you tell: Do i % 29 == 0 for each element. In declarative programming, you tell: Do what you wish, but the result must be equivalent to as you do i % 29 == 0 for each element.

Thus, LINQ may solve your task in a different manner than you can think. Keep that in mind. Free your mind from imperative and extend the usage of your LINQ.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer GFT
Poland Poland
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
Question[My vote of 2] Good and bad at the same time. Pin
Paulo Zemek30-Aug-13 18:34
mvaPaulo Zemek30-Aug-13 18:34 
AnswerRe: [My vote of 2] Good and bad at the same time. Pin
Ryszard Dżegan30-Aug-13 20:35
professionalRyszard Dżegan30-Aug-13 20:35 
GeneralRe: [My vote of 2] Good and bad at the same time. Pin
Paulo Zemek31-Aug-13 4:06
mvaPaulo Zemek31-Aug-13 4:06 
QuestionPeculiar Pin
Fredrik Bornander29-Aug-13 22:03
professionalFredrik Bornander29-Aug-13 22:03 
AnswerRe: Peculiar Pin
Ryszard Dżegan29-Aug-13 23:26
professionalRyszard Dżegan29-Aug-13 23:26 
GeneralRe: Peculiar Pin
Fredrik Bornander29-Aug-13 23:42
professionalFredrik Bornander29-Aug-13 23:42 
GeneralRe: Peculiar Pin
Ryszard Dżegan30-Aug-13 0:46
professionalRyszard Dżegan30-Aug-13 0:46 
GeneralMy vote of 5 Pin
qq39747225129-Aug-13 20:41
qq39747225129-Aug-13 20:41 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.