Having some trouble with Predicates in C#.
I have two sets of code (Both of which I believe should accomplish the same result) but one never works. The reason I am asking is I need this predicate to appear a few times with different elements and so I'd like to keep it as minimal as possible (the non working one is very simplistic while the other contains many lines).
1 Not working:
ItemViewModel item = (App.ViewModel.All_Items.Where(x => (x as ItemViewModel).Name == my_list_of_strings.ElementAt(2)) as ItemViewModel);
Also using "Select" instead of Where isn't working.
2 Working:
foreach (ItemViewModel it in App.ViewModel.All_Items)
{
if (item.Name == my_list_of_strings.ElementAt(2))
{
MessageBox.Show("Success!!");
item = it;
continue; // Leave loop
}
}
It's probably something stupid that I've overlooked but if anyone knows the solution, that would be great!
Thanks.
IEnumerable<T>.Where(Func<T, bool>) returns a collection however it looks like what you want is a single element. There are a couple of options:
IEnumerable<T>.FirstOrDefault(Func<T, bool>) // returns null if no element found
IEnumerable<T>.First(Func<T, bool>) // throws if no element is found
// These throw an error if more than one element exists that matches the query
IEnumerable<T>.SingleOrDefault(Func<T, bool>) // returns null if no element found
IEnumerable<T>.Single(Func<T, bool>) // throws if no element is found
In your example it would be:
// Just replace "Where" with "FirstOrDefault"
ItemViewModel item = (App.ViewModel.All_Items.FirstOrDefault(x => (x as ItemViewModel).Name == my_list_of_strings.ElementAt(2)) as ItemViewModel);
Related
I am confused. I have two statements that are the same and one works and the other receives the error - value cannot be null. r nparameter name source. From what I read I am receiving the error because something is null in my linq expression. However from what I can tell nothing is null.
The first if statement works. When a person selects 'Select' from a list of Burn Project a list of BurnPiles is displayed below the BurnProject list. (this works). Then when a person selects 'Select' from the list of BurnPiles a list of RequestedBurns is display below it. This gives me the null error. At one time it did work now it doesn't.
I am at a loss of what went wrong. I do know the RequestedBurn Table starts at record #2 but that shouldn't have anything to do with it. The BurnPile records that I have been using have associated RequestedBurns.
//when 'Select' is chosen from the list of burn projects the list of burn piles
//associated with that specific burn project is display below it.
if (burnerID != null)
{
ViewBag.BurnerID = burnerID.Value;
viewModel.BurnPiles = viewModel.BurnProjects.Where(
b => b.BurnerID == burnerID.Value).Single().BurnPiles;
}
//when 'Select' is chosen from the list of burn piles the list of requested
//burns associated with that specific burn pile is displayed below it.
if (burnPileID != null)
{
ViewBag.BurnPilesID = burnPileID.Value;
viewModel.RequestedBurns = viewModel.BurnPiles.Where(
x => x.BurnPilesID == burnPileID).Single().RequestedBurns;
}
If you look at documentation for Where or Single, you would see that source is the name of the parameter that represents your collection. So, it looks like you are trying to call a method on null reference, which would be the case if viewModel.BurnProjects = null or viewModel.BurnPiles = null.
viewModel.BurnPiles = viewModel.BurnProjects.Where(
b => b.BurnerID == burnerID.Value).Single().BurnPiles;
could be setting viewModel.BurnPiles to null.
or
viewModel.BurnPiles.Where(
x => x.BurnPilesID == burnPileID).Single()
is returning nothing so when you try and access RequestedBurns then it throws an exception.
SingleOrDefault also has an overload where you can simplify the expression a bit more. You can also combine it with the null conditional operator (if using at least C# 6).
if (burnerID != null)
{
ViewBag.BurnerID = burnerID.Value;
viewModel.BurnPiles = viewModel.BurnProjects.SingleOrDefault(b => b.BurnerID == burnerID.Value)?.BurnPiles;
}
For an invite function, I have a screen with a single field on it that lets users enter a code they've been given elsewhere. I then check in the controller to see which record is associated to this code. But my issue is with the controller blowing up if the code is not the first type is checks for (type 1 or type 2). So here what I believe it should be like(this doesn't work), although I realize the sloppiness I'm just new to c#.
Record rec = db.Records.Where(u = u.Code1).First();
If (rec != null)
{
Other code...
Exit controller
}
Record rec2 = db.Records.Where(u = u.Code2).First();
If (rec2 != null)
{
Other code...
Exit controller
}
Return to view - codes are invalid.
I've attempted other versions where I put the object check within the if statement but there didn't work either. Any ideas?
Use FirstOrDefault not First. First throws an exception if the collection is empty, FirstOrDefault returns default(T) if the collection is empty.
And actually, there are overloads of First and FirstOrDefault that take a condition parameter, so you don't need to call Where
ex:
var x = numbers.FirstOrDefault(n => n % 2 == 1);
This will return the first odd number in the collection, or 0 if there are none that meet the condition (or none at all.)
As explained by the MSDN docs on IEnumerable.First, this method throws an exception if there are no elements in the sequence
The First(IEnumerable) method throws an exception if
source contains no elements. To instead return a default value when
the source sequence is empty, use the FirstOrDefault method
So you should write
Record rec = db.Records.FirstOrDefault(u => u.Code1);
if(rec == null)
{
....
}
Notice that the same condition used in the Where extension could be used directly with FirstOrDefault
By the way, it is unclear what your expression is. The lambda expression (syntax is => ) should return a boolean value so, or your Code1 is a boolean variable or something is wrong in that line
Use db.Records.Where(u => u.Code1).FirstOrDefault();
Check the comparison value (ex. u.code1) if it has this property from the model before binding to the query.
So in the program I'm trying to run I receive two lists, one with objects that contain an id in string format (looks something like "bb_b1203322") and one list with the id's(which in this place is only named "b1203322" for reasons unknown) and a description of the actually id's meaning.
var forms = await _tRepository.GetAllFormsAsync(lastUpdate);
var formDefinitions = await _deRepository.GetAllFormDefintionsAsync();
foreach (var form in forms)
{
foreach (var def in formDefinitions)
{
if (form.SetupFormName.Contains(def.BLKID))
form.SetupFormName = def.DESCR;
}
}
return forms;
Now this piece of code does exactly what I want it to, but I'd rather have it as a lambda expression because ... reasons :)
Now I've tried several different things but with my current knowledge of lambda expressions I can't get it to work.
Try this code. Note that you can use it if formDefinitions with suitable DESCR always exists.
forms.ForEach(f => f.SetupFormName = formDefinitions.FirstOrDefault(fd =>
f.SetupFormName.Contains(fd.DESCR)).DESCR);
This code uses a bit of LINQ to find the definition:
foreach(var form in forms)
{
var def = formDefinitions.FirstOrDefault(x => form.SetupFormName.Contains(x.DESCR));
if(def != null)
form.SetupFormName = def.DESCR
}
As you can see, it's not really saving all that much code.
Please note:
As Jon correctly comments, the behavior of this code is a bit different from your original one. This code uses the first occurrence if there are multiple and your code uses the last occurrence.
If this is actually a use case for your code, replace FirstOrDefault with LastOrDefault.
Extending the code above, you can do something like this:
foreach(var tuple in forms.Select(x => new { Form = x,
Definition =
formDefinitions.FirstOrDefault(y =>
x.SetupFormName.Contains(y.DESCR)) })
.Where(x => x.Definition != null))
{
tuple.Form.SetupFormName = tuple.Definition.DESCR;
}
But as you can see, this gets messy real quick.
I have a list of objects as ICollection<objectA> listA. Now I'm trying to loop thru this listA and trying to match a condition and assign the result found into a variable. I tried below:
varB.someDesc = listA.FirstOrDefault(x=>x.ID == varB.ID).someDesc
Error complaining that x.ID is object null reference. When I put a break point, I can see there are many items in listA and there is an attribute ID. May I know what goes wrong?
I suggest validating the return value of FirstOrDefault() as follows:
var item = listA.FirstOrDefault(x=>x.ID == varB.ID);
if (item != null)
varB.someDesc = item.someDesc;
The error might not be exactly what you think it is.
Try this code.
varB.someDesc = listA.Where(x=>x.ID == varB.ID).FirstOrDefault().someDesc
you better check Object Null before assigning.
I have an ordered list of People. I have a person that I know exists in that collection. How can I determine which person is next in the list?
You could do something like this:
IEnumerable<Person> persons = ..
var firstPersonAfterJack = persons.SkipWhile(p => p.Name != "Jack")
.ElementAt(1); //Zero-indexed, means second
The idea is to produce a sequence resulting in skipping elements until you meet the condition, then take the second element of that sequence.
If there's no guarantee that the query will return a result (e.g. a match is never found, or is the last element of the sequence), you could replace ElementAt with ElementAtOrDefault, and then do a null-test to check for success / failure.
I notice you say in your question that you have an ordered list of people. If you could explain what that means in more detail, we might be able to provide a better answer (for example, we may not have to linear-search the sequence).
SkipWhile is a method that takes a predicate and skips everything until the predicate is false. It returns that element and everything after.
var remainingPeople = collectionOfPeople.SkipWhile(p => !isThePerson(p));
if (remainingPeople.Count() == 1)
{
// the person was the last in the list.
}
var nextPerson = remainingPeople.Skip(1).First();
where isThePerson is a method that takes a person and returns true if it is the person you are interested it.
You can use code like this:
String toDir = Environment.GetCommandLineArgs().SkipWhile(x => x != "/to").Skip(1).Take(1).FirstOrDefault();
This value gets == null if "/to" command line argument not given, non-null if path was provided.