. Several traps of "delay" attribute in net

Source: Internet
Author: User
Tags foreach anonymous object bool execution functions net tostring
. NET development so far, in fact everywhere have "delay (Lazy)" Traces, a small "laziness" to bring us a lot of flexibility 1. The key to "delay" is "processing data only when needed", and Lao Zhao has mentioned similar concepts in several articles, such as higher-order functions, delegates and anonymous methods, and "Are you good at using anonymous functions?" 》。 However, the "delay" itself will also bring you some traps, some traps you may have been encountered. This article summarizes the common pitfalls of latency and gives a solution.

. NET development so far, in fact everywhere have "delay (Lazy)" Traces, a small "laziness" to bring us a lot of flexibility 1. The key to "delay" is "processing data only when needed", and Lao Zhao has mentioned similar concepts in several articles, such as higher-order functions, delegates and anonymous methods, and "Are you good at using anonymous functions?" 》。 However, the "delay" itself will also bring you some traps, some traps you may have been encountered. This article summarizes the common pitfalls of latency and gives a solution.

Repeat operation

Problem

"Delay" is intended to "reduce the calculation," but if you use it incorrectly, it is likely to result in "duplicate counting." For example, we first construct a method that takes an argument n, returns a Func<int, Bool> object:

Static Func<int, bool> divideby (int n)
{
return x =>
{
BOOL divisible = x% n = 0;
Console.WriteLine (
"{0} can is divisible by {1}?" {2} ",
x, N, divisible? "Yes": "No");
return divisible;
};
}

Func<int, the Bool> object returns a Boolean value that indicates whether X can be divisible by N, based on the argument x passed in. In this process, you will also output a single sentence to the console, for example: "Can divisible by 3?" No ". Every time you see this sentence, it means "after a judgment." So do you know what the following code will output?

list<int> values = new list<int> ();
for (int i = 0; i < i++) values. ADD (i);

var dividebytwo = values. Where (Divideby (2));
var dividebytwoandthree = Dividebytwo.where (Divideby (3));
var dividebytwoandfive = Dividebytwo.where (Divideby (5));

foreach (var i in dividebytwoandthree) {}
foreach (var i in dividebytwoandfive) {}

The results are as follows:

0 can divisible by 2? Yes
0 can divisible by 3? Yes
1 can divisible by 2? No
2 can divisible by 2? Yes
2 can divisible by 3? No
3 can divisible by 2? No
4 can divisible by 2? Yes
4 can divisible by 3? No
5 can divisible by 2? No
6 can divisible by 2? Yes
6 can divisible by 3? Yes
7 can divisible by 2? No
8 can divisible by 2? Yes
8 can divisible by 3? No
9 can divisible by 2? No
0 can divisible by 2? Yes
0 can divisible by 5? Yes
1 can divisible by 2? No
2 can divisible by 2? Yes
2 can divisible by 5? No
3 can divisible by 2? No
4 can divisible by 2? Yes
4 can divisible by 5? No
5 can divisible by 2? No
6 can divisible by 2? Yes
6 can divisible by 5? No
7 can divisible by 2? No
8 can divisible by 2? Yes
8 can divisible by 5? No
9 can divisible by 2? No

Do you find that whether you're traversing the dividebytwoandthree and dividebytwoandfive sequences, you re judging from the original values sequence that each element can be divisible by 2? This is the latency feature of the "Where" in. NET 3.5, and if you don't realize it here, you can generate duplicate computations that waste computational power.

Solution

The way to solve this problem is to make "mandatory calculations" at the right time. For example:

var dividebytwo = values. Where (Divideby (2)). ToList ();
var dividebytwoandthree = Dividebytwo.where (Divideby (3));
var dividebytwoandfive = Dividebytwo.where (Divideby (5));

The result becomes:

0 can divisible by 2? Yes
1 can divisible by 2? No
2 can divisible by 2? Yes
3 can divisible by 2? No
4 can divisible by 2? Yes
5 can divisible by 2? No
6 can divisible by 2? Yes
7 can divisible by 2? No
8 can divisible by 2? Yes
9 can divisible by 2? No
0 can divisible by 3? Yes
2 can divisible by 3? No
4 can divisible by 3? No
6 can divisible by 3? Yes
8 can divisible by 3? No
0 can divisible by 5? Yes
2 can divisible by 5? No
4 can divisible by 5? No
6 can divisible by 5? No
8 can divisible by 5? No

At this point, when the dividebytwo sequence is obtained, the computation is done immediately, so that elements such as 1,3,5 are not repeated when the two are traversed.

Abnormal traps

Problem

Do you know what the problem is with the following code?

public static ienumerable<string> ToString (ienumerable<int> source)
{
if (Source = null)
{
throw new ArgumentNullException ("source");
}

foreach (int item in source)
{
Yield return item. ToString ();
}
}

If you don't see it, run the code:

static void Main (string[] args)
{
Ienumerable<string> values;
Try
{
values = ToString (null);
}
catch (ArgumentNullException)
{
Console.WriteLine ("Passed the null source");
Return
}

foreach (var s in values) {}
}

Can you tell me if the code above will throw an exception? From the intent of the code, we check that the parameter is null at the beginning of the ToString method and then throw an exception-this should be captured by the catch statement. But in fact, the code does not really throw an exception until the foreach executes. This "delay" execution violates our implementation intent. Why is that? You can reverse-compile with. NET Reflector and see what the equivalent C # implementation of the yield statement is, and everything is clear.

Solution

For this problem, we can generally use a pair of public and private methods to work with:

public static ienumerable<string> ToString (ienumerable<int> source)
{
if (Source = null)
{
throw new ArgumentNullException ("source");
}

return tostringinternal (source);
}

private static ienumerable<string> tostringinternal (ienumerable<int> source)
{
foreach (int item in source)
{
Yield return item. ToString ();
}
}

May I take a look at the current C # code implementation?

Resource Management

Problem

Because it is deferred execution, some of the simplest code patterns may be corrupted. For example:

Static func<string> ReadAllText (string file)
{
using (Stream stream = File.openread (File))
{
StreamReader reader = new StreamReader (stream);
Return reader. ReadToEnd;
}
}

Using using to manage open shutdown of files is the easiest thing to do, but now if you get the Func<string> object from the ReadAllText (@ "C:\abc.txt") method, Throws a objectdisposedexception when it executes. This is due to the order in which we intended:

Open File

Read content

Close File

Because of the "delay" attribute, this order has been changed to:

Open File

Close File

Read content

How can this not be wrong?

Solution

Some friends say this is easy:

Static func<string> ReadAllText (string file)
{
using (Stream stream = File.openread (File))
{
StreamReader reader = new StreamReader (stream);
string text = Reader. ReadToEnd ();

return () => text;
}
}

No exception was thrown, but it also lost the "delay" feature. We have to make it possible to open the file when the delegate object is invoked:

Static func<string> ReadAllText (string file)
{
Return () =>
{
using (Stream stream = File.openread (File))
{
StreamReader reader = new StreamReader (stream);
Return reader. ReadToEnd ();
}
};
}

It is worth mentioning that the using is fully compatible with the yield statement. In other words, you can write code like this:

Static ienumerable<string> alllines (string file)
{
using (Stream stream = File.openread (File))
{
StreamReader reader = new StreamReader (stream);
while (!reader. Endofstream)
{
Yield return reader. ReadLine ();
}
}
}

It also shows how powerful the C # compiler is, and it helps us solve very important problems.

Closed-pack sharing

Problem

In fact, this question has been discussed many times, here to mention the main is to maintain the integrity of the content. What do you think the following code results?

list<action> actions = new list<action> ();
for (int i = 0; i < i++)
{
Actions. Add (() => Console.WriteLine (i));
}

foreach (Var A in actions) a ();

The result is 10 10, which is described in the article "alert to the sharing of variables by anonymous methods," which is summed up by the fact that each action shares a closure, causing the "I" to be not independent.

Solution

The only way to solve this problem is to make the values of different closures accessible to each other independently. Such as:

list<action> actions = new list<action> ();
for (int i = 0; i < i++)
{
int j = i; New Code
Actions. Add (() => Console.WriteLine (j));
}

foreach (Var A in actions) a ();

What else do you think about the latency feature?



Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.