关于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 OOPImperative Programming ParadigmDeclarative Programming ParadigmC#LINQExtension MethodsExpression 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:

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:

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:

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:

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:

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

We could put the boosted algorithm here:

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:

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:

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:

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.

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值