Set several class values using LINQ expression - c#

I have the following two LINQ statements which set different values in the same item in a list
List<MyClass> myList = GetList();
myList.Where(x => x.Name == "someName").Select(x => x.MyArray = someList.ToArray()).ToList();
myList.Where(x => x.Name == "someName").Select( x => x.AnotherValue = GetValue()).ToList();
Is it possible to combine this so both are set in the one expression?

myList
.Where(x => x.Name == "someName")
.ToList()
.ForEach(x => {
x.MyArray = someList.ToArray();
x.AnotherValue = GetValue();
});
Why are you calling ToList() at the end of each of those expressions and discarding the result?
Also, Jon Skeet is right that this is an abuse of LINQ, and especially so in your original form: It's explicit that LINQ expressions aren't even necessarily expected to be fully enumerated. The fact that you needed those ToList() calls to make anything happen should have given you a grave and queasy sense that you were misusing a language feature. When you have to do something weird to use your chosen construct instead of the usual way of doing it, finish getting it to work (because weird is cool), and then go back and redo it the boring, lame way before you check it in.
What advantage do you see in the LINQ + ForEach() version above, compared to this version?
foreach (var x in myList.Where(x => x.Name == "someName"))
{
x.MyArray = someList.ToArray();
x.AnotherValue = GetValue();
}
The old-style loop version is shorter, instantly understandable because it's the default idiom, and IMO cleaner. You don't have to do everything with LINQ.
N.B., ForEach() isn't LINQ; it's a member of List<T>. That's why you have to call ToList() to use it.

Just use the lambda operator to pass an entire lambda expression defined inside a
{...} block:
myList.Where(x => x.Name == "someName").Select(x => { x.MyArray = someList.ToArray(); x.AnotherValue = GetValue(); return x;}).ToList();

Related

Why is LINQ not letting me OrderBy one of my data points?

I have this code:
return inventoryItems
.Where(i => 0 < String.Compare(i.ID, ID))
.Take(CountToFetch);
...but I want to order the results, like so:
return inventoryItems
.Where(i => 0 < String.Compare(i.ID, ID))
.Take(CountToFetch)
.OrderBy(i.pksize);
...however, the final i is red/out of scope. Why? Trying to position the OrderBy() prior to the Take() makes no difference.
return inventoryItems.Where(i => 0 < String.Compare(i.ID, ID))
.Take(CountToFetch)
.OrderBy(i => i.pksize);
And maybe you should change OrderBy and Take order to make results predictable:
return inventoryItems.Where(i => 0 < String.Compare(i.ID, ID))
.OrderBy(i => i.pksize)
.Take(CountToFetch);
There's no i in the OrderBy.
You want OrderBy(i => i.pksize) (or indeed x => x.pksize, whatever => whatever.pksize, etc.)
The sort of syntax that allows from x in something where x.IsOkay orderby x.Priority etc. uses the same variable label all the way through, but it gets turned into more than one lambda expression, which are each separate from each other. (something.Where(x => x.IsOkay).OrderBy(x => x.Priority), but they need to each be full expressions.
It looks like you've tried to use the i you declared in Where. If you look closely at the brackets, you should see that it is actually no longer in scope within the OrderBy. So the first problem is you're trying to use an out-of-scope variable.
OrderBy actually needs to be able to check the property on each element in the IEnumerable, so it doesn't make sense to only pass it the value of one element's property. It needs to know how to get the property for each element, which is why you typically pass in a lambda expression, anonymous delegate or method.
As the others pointed out, you can solve the problem by using an lambda expression like you did in Where:
.OrderBy(i => i.pksize)

How to optimize a LINQ with minimum and additional condition

Asume we have a list of objects (to make it more clear no properties etc.pp are used)
public class SomeObject{
public bool IsValid;
public int Height;
}
List<SomeObject> objects = new List<SomeObject>();
Now I want only the value from a list, which is both valid and has the lowest height.
Classically i would have used sth like:
SomeObject temp;
foreach(SomeObject so in objects)
{
if(so.IsValid)
{
if (null == temp)
temp = so;
else if (temp.Height > so.Height)
temp = so;
}
}
return temp;
I was thinking that it can be done more clearly with LinQ.
The first approach which came to my mind was:
List<SomeObject> sos = objects.Where(obj => obj.IsValid);
if(sos.Count>0)
{
return sos.OrderBy(obj => obj.Height).FirstOrDefault();
}
But then i waas thinking: In the foreach approach i am going one time through the list. With Linq i would go one time through the list for filtering, and one time for ordering even i do not need to complete order the list.
Would something like
return objects.OrderBy(obj => obj.Height).FirstOrDefault(o => o.IsValid);
also go twice throught the list?
Can this be somehow optimized, so that the linw also only needs to run once through the list?
You can use GroupBy:
IEnumerable<SomeObject> validHighestHeights = objects
.Where(o => o.IsValid)
.GroupBy(o => o.Height)
.OrderByDescending(g => g.Key)
.First();
This group contains all valid objects with the highest height.
The most efficient way to do this with Linq is as follows:
var result = objects.Aggregate(
default(SomeObject),
(acc, current) =>
!current.IsValid ? acc :
acc == null ? current :
current.Height < acc.Height ? current :
acc);
This will loop over the collection only once.
However, you said "I was thinking that it can be done more clearly with LinQ." Whether this is more clear or not, I leave that up to you to decide.
You can try this one:
return (from _Object in Objects Where _Object.isValid OrderBy _Object.Height).FirstOrDefault();
or
return _Objects.Where(_Object => _Object.isValid).OrderBy(_Object => _Object.Height).FirstOrDefault();
Would something like
return objects.OrderBy(obj => obj.Height).FirstOrDefault(o => o.IsValid);
also go twice throught the list?
Only in the worst case scenario, where the first valid object is the last in order of obj.Height (or there is none to be found). Iterating the collection using FirstOrDefault will stop as soon as a valid element is found.
Can this be somehow optimized, so that the linw also only needs to run
once through the list?
I'm afraid you'd have to make your own extension method. Considering what I've written above though, I'd consider it pretty optimized as it is.
**UPDATE**
Actually, the following would be a bit faster, as we'd avoid sorting invalid items:
return object.Where(o => o.IsValid).OrderBy(o => o.Height).FirstOrDefault();

Can you set/update a value in real-time within a LINQ statement during iteration?

Based on a proposed answer to my other question here... is it possible to update a variable during LINQ enumeration so you can use it as part of a test?
For instance, is anything like this possible?
// Assume limitItem is of type Foo and sourceList is of type List<Foo>
// Note the faux attempt to set limitItemFound in the TakeWhile clause
// That is what I'm wondering.
sourceList.Reverse()
.TakeWhile(o => (o != limitItem) && !limitItemFound; limitItemFound = limitItemFound || (o == limitItem) )
.FirstOrDefault(o => ...);
This would make the search inclusive of limitItem.
For LINQ to Objects (which takes delegates) then you can, yes - using a statement lambda:
sourceList.Reverse()
.TakeWhile(o => {
... fairly arbitrary code here
return someValue;
})
.FirstOrDefault(o => ...);
I would strongly discourage you from doing this though. It will make it much harder to understand what's going on, because you're losing the declarative nature of idiomatic LINQ code.

using LINQ convertAll when some conversions may be null

i have the following code
people = positions.ConvertAll(r=> r.Person).ToList();
but in some cases "Person" is going to be null, in these cases i simply don't want to add them into the converted collection (i dont want null items)
what is the best way of achieving this. Can you have a conditional convertall ??
With LINQ, you can do:
positions.Where(r => r.Person != null)
.Select(r => r.Person)
.ToList();
The ConvertAll method is not part of LINQ; it's an instance method on List<T>.
If you want to stick with that, you can do:
positions.FindAll(r => r.Person != null)
.ConvertAll(r => r.Person);
Do note that this is subtly different because the result of the filter and the projection are both List<T>s, rather than streaming queries. The final result should be the same though.
people = positions
.Where(r => r.Person !=null).ToList()
.ConvertAll(r=> r.Person);
Use Where to filter out the null occurrences then use Select:
people = positions.Where(p => p.Person != null).Select(r => r.Person).ToList();

LINQ query to select based on property

IEnumerable<MyClass> objects = ...
foreach(MyClass obj in objects)
{
if(obj.someProperty != null)
SomeFunction(obj.someProperty);
}
I get the feeling I can write a smug LINQ version using a lambda but all my C# experience is 'classical' i.e more Java-like and all this Linq stuff confuses me.
What would it look like, and is it worth doing, or is this kind of Linq usage just seen as showing off "look I know Linq!"
LINQ itself doesn't contain anything for this - I'd would use a normal foreach loop:
foreach (var value in objects.Select(x => x.someProperty)
.Where(y => y != null))
{
SomeFunction(value);
}
Or if you want a query expression version:
var query = from obj in objects
let value = obj.SomeProperty
where value != null
select value;
foreach (var value in query)
{
SomeFunction(value);
}
(I prefer the first version, personally.)
Note that I've performed the selection before the filtering to avoid calling the property twice unnecessarily. It's not for performance reasons so much as I didn't like the redundancy :)
While you can use ToList() and call ForEach() on that, I prefer to use a straight foreach loop, as per Eric's explanation. Basically SomeFunction must incur a side-effect to be useful, and LINQ is designed with side-effect-free functions in mind.
objects.where(i => i.someProperty != null)
.ToList()
.ForEach(i=> SomeFunction(i.someProperty))
Although it can be done with Linq, sometimes its not always necessary. Sometimes you lose readability of your code. For your particular example, I'd leave it alone.
One option is to use the pattern outlined in the book Linq In Action which uses an extension method to add a ForEach operator to IEnumerable<>
From the book:
public static void ForEach<T> (this IEnumerable<T> source, Action<T> func)
{
foreach (var item in source)
func(item)
}
Then you can use that like this:
(from foo in fooList
where foo.Name.Contains("bar")
select foo)
.ForEach(foo => Console.WriteLine(foo.Name));
LINQ is used to create a result, so if you use it to call SomeFunction for each found item, you would be using a side effect of the code to do the main work. Things like that makes the code harder to maintain.
You can use it to filter out the non-null values, though:
foreach(MyClass obj in objects.Where(o => o.someProperty != null)) {
SomeFunction(obj.someProperty);
}
You can move the if statement into a Where clause of Linq:
IEnumerable<MyClass> objects = ...
foreach(MyClass obj in objects.Where(obj => obj.someProperty != null)
{
SomeFunction(obj.someProperty);
}
Going further, you can use List's ForEach method:
IEnumerable<MyClass> objects = ...
objects.Where(obj => obj.someProperty != null).ToList()
.ForEach(obj => SomeFunction(obj.someProperty));
That's making the code slightly harder to read, though. Usually I stick with the typical foreach statement versus List's ForEach, but it's entirely up to you.

Categories

Resources