简体   繁体   中英

Func<> or method in test code?

I saw this loop in test code:

foreach ( StuffId Id in Result.GetIdList() )
{
    if ( Id.Level == 3 )
    {
        Level3Id = Id.ToString();
    }
    if ( Id.Level == 5 )
    {
        Level5Id = Id.ToString();
    }
}

Other tests imply that either there is only one Id for each level or when there are multiples for each level then the Id will be the same.

Being slightly obsessed with LINQ right now, I first refactored to this:

IEnumerable<StuffId> Ids = Result.GetIdList();

Level3Id = Ids.Where( x => x.Level == 3 ).First().Id.ToString();
Level5Id = Ids.Where( x => x.Level == 5 ).First().Id.ToString();

Then the code repetition bothered me so I refactored to this:

IEnumerable<StuffId> Ids = Result.GetIdList();
Func<int,string> IdFromLevel = 
    level => Ids.Where( x => x.Level == level ).First().Id.ToString();

Level3Id = IdFromLevel(3);
Level5Id = IdFromLevel(5);

A colleague wondered why I didn't use a method in place of the delegate. My reasoning is a method would be slightly more 'messy' because I'd have to additionally pass in the collection and that using a delegate is no big deal for a simple test (for which terse, readable and no branching are good qualities).

I had a look on SO, of course, and found this seemingly relevant question:

C#: Func<> instead of methods?

where the consensus seems to favour a method over a delegate. Does the same apply in my case?

It is a question of reuse. If you are using methods that might be reusable in other cases, you should define them seperately.
But if you have only short statements that additionally vary, you should stick with anonymous functions/lambda-expressions, these might result in a better runtime-behavior.

From SO :

There is no advantage in the code you posted. In your code, using the delegate just adds complexity as well as an extra runtime cost - so you're better off just calling the method directly.

However, delegates have many uses. "Passing around" to other methods is the primary usage, though storing a function and using it later is also very useful.

LINQ is built on top of this concept entirely. When you do:

var results = myCollection.Where(item => item == "Foo");

You're passing a delegate (defined as a lambda: item => item == "Foo") to the Where function in the LINQ libraries. This is what makes it work properly.

I would go with the first block:

foreach (StuffId Id in Result.GetIdList())
{
    if (Id.Level == 3)
    {
        Level3Id = Id.ToString();
    }
    if (Id.Level == 5)
    {
        Level5Id = Id.ToString();
    }
}

This does loop the collection only once. I see you don't worry about performance here, but for me its not a matter of performance or optimization. It's a matter of doing something logically correct way. Why do something twice if it could be in one step (provided readability is not hurt).

The added benefit is that you don't have the dilemma between Func<,> and method, and the related complexities. As far as number of characters or ease of typing goes, its almost the same, except you're writing it horizontally (in the second case) than vertically. You can even write the above in two lines inside the foreach block.

If you're bent on writing the two actions separately, I would make the choice based on whether the function has relevance outside the scope of current method in this case. It seems to me that its a trivial predicate which is relevant only for the two assignments. So I like:

var Ids = Result.GetIdList();
Func<int, string> IdFromLevel = level => Ids.Single(x => x.Level == level).Id.ToString();

Level3Id = IdFromLevel(3);
Level5Id = IdFromLevel(5);

Prefer Single here over First ..

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM