using LINQ convertAll when some conversions may be null - c#

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();

Related

Set several class values using LINQ expression

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();

Filter in linq with ID's in a List<int>

I need do a filter that request data with a parameter included in a list.
if (filter.Sc.Count > 0)
socios.Where(s => filter.Sc.Contains(s.ScID));
I try on this way but this not work, I tried also...
socios.Where( s => filter.Sc.All(f => f == s.ScID));
How I can do a filter like this?
socios.Where(s => filter.Sc.Contains(s.ScID));
returns a filtered query. It does not modify the query. You are ignoring the returned value. You need something like:
socios = socios.Where(s => filter.Sc.Contains(s.ScID));
but depending on the type of socios the exact syntax may be different.
In addition to needing to use the return value of your LINQ .Where(), you have a potential logic error in your second statement. The equivalent logic for a .Contains() is checking if Any of the elements pass the match criteria. In your case, the second statement would be
var filteredSocios = socios.Where( s => filter.Sc.Any(f => f == s.ScID));
Of course if you can compare object-to-object directly, the .Contains() is still adequate as long as you remember to use the return value.

and operator in Linq and select distinct values using linq

I am new to .net. I have a form in which there are two comboboxes cbProduct and cbBrandName and also a label lblPrice.
I am trying to implement the below code but it is showing blue scribbles to &&.
(Error: operator '&&' cannot be applied to operands of type 'lambda expression' and 'lambda expression')
I tried the below code: (not working)
lblPrice.Text = string.Empty;
lblPrice.Text = doc.Descendants("items"
).Where((x => x.Element("productname"
).Value.Equals(cbProduct.SelectedItem.ToString())) && /*blue scribbles to '&&'*/
(y => y.Element("brandname").Value.Equals(cbBrandName.SelectedItem.ToString()
))).Select(k => k.Element("price"
).Value).ToString();
My other question is that i want to make the selected values of cbProduct as distinct. The below code takes all the values instead of distinct values:
cbProduct.Items.AddRange(doc.Descendants("items"
).Select(x => x.Element("productname").Value
).ToArray<string>());//adds all products
cbProduct.SelectedIndex = 0;
giving any one answer is ok
Please assist me
Thanks in advance
It looks like you are passing 2 lambdas to the Where function and trying to logical-and (&&) them together. You can't do that. The && has to occur inside the Where lambda. Or you can chain 2 Where functions together. Something like this:
lblPrice.Text = doc.Descendants("items")
.Where(x => x.Element("productname").Value.Equals(cbProduct.SelectedItem.ToString()) &&
x.Element("brandname").Value.Equals(cbBrandName.SelectedItem.ToString()))
.Select(k => k.Element("price").Value).ToString();
The other issue I see is you are ending your query with a select, but never actually enumerating it. You probably want to do something like this:
lblPrice.Text = doc.Descendants("items")
.Where(x => x.Element("productname").Value.Equals(cbProduct.SelectedItem.ToString()) &&
x.Element("brandname").Value.Equals(cbBrandName.SelectedItem.ToString()))
.Select(k => k.Element("price").Value)
.FirstOrDefault();
Which will return the string you are looking for, or null if nothing exists (so you probably want to skip the final .ToString() call in this case, since you are already returning a string from Select and .ToString() on a null will throw an exception).
For the first question, it looks like you just want to select the one price. This code will work, assuming that the item is found by the .Single(). It will throw otherwise, in which case you should use .SingleOrDefault() and check for null on the found item.
lblPrice.Text =
doc.Descendants("items")
.Single(x => x.Element("productname").Value == cbProduct.SelectedItem.ToString() &&
x.Element("brandname").Value == cbBrandName.SelectedItem.ToString())
.Element("price").Value;
For the second question, you need to close off your .Select with a bracket, then you can call .Distinct() and .ToArray() to filter to distincts and project the result to string[]. I've also thrown an .OrderBy() in there, as there's nothing more annoying than a ComboBox in a random order. Try this:
cbProduct.Items.AddRange(doc.Descendants("items")
.Select(item => item.Element("productname").Value)
.Distinct()
.OrderBy(item => item)
.ToArray());

Add on condition with Lambda

Is there a way I can do something like this with Lambda expressions?
responses.Add(sr).Where(v.Responses.TryGetValue(v.responseType, out sr));
I want to use lambda expressions or a ternary operator instead of a typical if expression.
NB:
responses is a List<string> type.
v.Responses is a Dictionary of <enum ResponseType, string>
v is some object
sr is a string.
What you want to do is:
string sr;
if (v.Responses.TryGetValue(v.responseType, out sr))
responses.Add(sr);
There is no way to ease the syntax and get the same performance.
But you could do:
responses.AddRange( v.Responses.Where( p => p.Key == v.responseType )
.Select( p => p.Value ) );
You may want to think about what the last one is doing, because it is kind of stupid...
EDIT: the reason why it is stupid is because the last expression translates into:
foreach(var pair in v.Responses)
{
if (pair.Key == v.responseType)
responses.Add(pair.Value);
}
So if your ResponseType enumeration had 6 million entries, the program would iterate over the entire set of keys to find the correct entry. In your case, since you already know the key, you should use v.Responses[key] as it is extremely fast (see in which cases dictionaries must be use).
LINQ is not supposed to modify collections.
Couldn't you simply do something like this:
string sr;
if(v.Responses.TryGetValue(v.responseType, out sr))
responses.Add(sr);
If I understand your question correctly, this might do what you're looking for.
Based on your example code, I'm assuming that your object "v" contains a field named "responseType" of type RepsonseType.
var responses = v.Responses
.Where(r => r.Key == v.responseType)
.Select(r => r.Value)
.ToList();

how do I return a filtered list using linq?

I'm not sure if my title is correct, but linq should pull the right experts in to help the title and answer the question.
If I have a list of People, how do I return a list of PeopleWrappers minus "Dave" and "Jane"?
query looks like this right now:
List<Person> People = CreatListofPersons();
People.Select(t => new PeopleWrapper(t)).ToArray();
People.Where( x => x.Name!="Dave" && x.Name!="Jane")
.Select(t => new PeopleWrapper(t))
.ToArray();
LINQ has a list of extension methods that allow you to filter or project (which you already to with Select()). In your case you can use Where() to specify which elements you want to let pass, in your example all persons whose name is neither Dave nor Jane.
does it matter if "Where" comes before
"Select" or after?
You typically want to filter as soon as you can, otherwise you will have to iterate and/or project over items you don't want to have anyway.
Conceptually though, yes, you can put there where() filter later but in your case you are dealing with a PeopleWrapper after you project with Select() - since the Where() extension method is using this data type as input its condition I don't think it would make much sense - you would filter people wrappers, not persons.
return People.Where(p => p.Name != "Dave" && p.Name != "Jane");
You can use Where to get just the elements that match a condition:
People.Where(p => p.name != "Dave" && p.name != "Jane" ).Select(t => new PeopleWrapper(t)).ToArray()

Categories

Resources