Hello I try To Convert My old code to new version with Linq but i have problem for do it
old :
foreach (var item in NetworkInterface.GetAllNetworkInterfaces())
{
if (item.NetworkInterfaceType == NetworkInterfaceType.Ethernet)
{
lstTrace.Items.Add(item.Name);
}
}
to this:
lstTrace.Items.Add(
NetworkInterface.GetAllNetworkInterfaces()
.Where(nic => nic.NetworkInterfaceType == NetworkInterfaceType.Ethernet)
.FirstOrDefault()
.Name
);
But it just returns one result.
How can I get all found items?
I would not recommend to create 'one-liner' code which spans for 5 lines and mixes both data selection and filling list view. Make both things easy to read and understand. Split (1) retrieving and filtering data with (2) assigning data to list view:
var ethernetInterfaceNames =
from i in NetworkInterface.GetAllNetworkInterfaces()
where i.NetworkInterfaceType == NetworkInterfaceType.Ethernet
select i.Name;
foreach(var name in ethernetInterfaceNames)
lstTrace.Items.Add(name);
I would also move getting ethernet interface names to separate method or layer. Thus you will split business logic and presentation logic. You can use AddRange here, but it willl not make your code any simpler:
lstTrace.Items.AddRange(ethernetInterfaceNames.Select(n => new ListViewItem(n)).ToArray())
I believe simple foreach loop is far more readable.
You need to use the AddRange method to add multiple items and then you just need to use select to get the Names of your nics.
Your current code is using FirstOrDefault which will only ever return a single value (the first) from your enumerable.
lstTrace.Items.AddRange(
NetworkInterface
.GetAllNetworkInterfaces()
.Where(nic => nic.NetworkInterfaceType == NetworkInterfaceType.Ethernet)
.Select(nic => nic.Name)
.ToArray()
);
Additionally xanatos's comment on your question is worth repeating here. Your previous code worked fine, and was readable. Doing this with LINQ isn't going to make your code faster and I would probably say makes it harder to read if anything. While the above code should work I would seriously consider just keeping your original code.
lstTrace.Items.AddRange(NetworkInterface.GetAllNetworkInterfaces().Where(nic => nic.NetworkInterfaceType == NetworkInterfaceType.Ethernet).Select(a => a.Name).ToArray());
You are only getting one, because you are only asking for one: FirstOrDefault will give you the first member of the sequence or the type's default value if its empty.
What you need to do is project the sequence to what you really need using Select:
lstTrace.Items
.AddRange(NetworkInterface.GetAllNetworkInterfaces()
.Where(nic => nic.NetworkInterfaceType == NetworkInterfaceType.Ethernet)
.Select(item => item.Name));
Here is an example on how to use AddRange
var projects = multi.Read<ProjectDto>();
var projectAct = multi.Read<ProjectActivity>();
foreach (var project in projects)
{
project.ProjectActivities = new List<ProjectActivity>();
project.ProjectActivities.AddRange(projectAct.Where(x => x.ProjectId == project.ProjectId));
}
Note: One cannot use the addrange on a data type IEnumerable only on the data type list.
Related
Hi I have a question with regards to the efficiency of iterating through a list of values.
I am wondering say you have to look through a list of values pulling out those values that match your current search criteria, does it make sense to remove the match you have found once you have found it, resulting in a smaller list of values to search through on the next iteration. Or does this make little difference. Here is my code.
foreach (Project prj in projectList)
{
string prjCode = prj.Code;
var match = usersProjects.FirstOrDefault(x => x.Code == prjCode);
if (match != null)
{
usersProjects.Remove(match);
//More logic here
}
}
Basically I am searching for a project code that corresponds to a user from a list of all projects.
Say there are 50 projects, and the user has access to 20 of them. Does removing the found project every loop reducing the overall project count make the iteration more efficient? Thanks.
I wouldn't recommend changing the list - that, itself, is slow, order O(n).
Use a prepared lookup to do what you want instead of FirstOrDefault()
var projectLookup = usersProjects.ToLookup((x) => x.Code);
foreach (Project prj in projectList)
{
string prjCode = prj.Code;
var match = projectLookup[prjCode].FirstOrDefault()
if (match != null)
{
//More logic here
}
}
Note that ToLookup() is expensive so you want to retain the lookup if possible - consider recreating it only when userProjects changes. After that, actually using the lookup to retrieve a match requires only constant time.
I would suggest using a group join for this:
var matches =
from prj in projectList
join x in usersProjects on prj.Code equals x.Code into xs
where xs.Any()
select xs.First();
Actually, a slightly better query would be:
var matches =
from prj in projectList
join x in usersProjects on prj.Code equals x.Code into xs
from x1 in xs.Take(1)
select x1;
If you then need to remove them from the usersProjects list you would need to do this:
foreach (var match in matches)
{
usersProjects.Remove(match);
}
But, if you just want to know what's left in the usersProjects if you removed the matches you could then just do this:
var remainingUsersProjects = usersProjects.Except(matches);
At the end of all of this the only thing you need to do is time all of the options to see what is faster.
But I would think that it really won't matter unless your lists are huge. Otherwise I'd go with the simplest to understand code so that you can maintain your project in the future.
Instead of loop and multiple FirstOrDefault() calls, you can use simple Where() method to get all user projects:
userProjects = userProjects.Where(up => projectList.All(p => up.Code != p.Code))
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();
I have below legacy code in my application and would like to optimize it. arrayOfAttrValue has unique attributes. Can I use LINQ to acheive the loop optimization? If so then can you please show me how?
foreach (AttrValue attr in arrayOfAttrValue)
{
switch(attr.Attribute)
{
case Constants.Gender
mymodel.Gender = attr.Value;
break;
case Constants.Identifier
mymodel.AppIdentifier = attr.Value;
break;
}
}
My intention is not necessarily to use LINQ only. Any other way to minimize the loop would also help.
Thanks.
No, you can't do it in "true" LINQ because LINQ is about producing new objects from old objects. Here mymodel is a preexisting object that you want to modify.
You could use the Array.ForEach or the List.ForEach but
They aren't "true" LINQ and
The resulting code would be equivalent (a little slower because there would be a delegate)
Still, the downvoter probably wanted some LINQ, so I'll give some LINQ:
arrayOfAttrValue.All(attr => {
mymodel.Gender = attr.Attribute == Constants.Gender ? attr.Value : mymodel.Gender;
mymodel.AppIdentifier = attr.Attribute == Constants.Identifier ? attr.Value : mymodel.AppIdentifier;
return true;
});
One less line, ignoring the {} lines.
You have list of attributes, which represent key-value pairs. Natural way to keep such data structures is a dictionary. So, convert your input data format to dictionary:
var attributes = arrrayOfAttrValue.ToDictionary(a => a.Attribute, a => a.Value);
Or if each attribute is not unique in your array, dictionary creation will be more difficult, but that's data format you have. In order to make working with your data easier you should convert them to handy format:
var attributes = arrayOfAttrValue
.GroupBy(a => a.Attribute)
.ToDictionary(g => g.Key, g => g.Select(a => a.Value).Last());
After creating attributes dictionary, you can simply check if you have value for attribute and assign that value to model property. Attributes retrieving now simple and clear for any reader:
if (attributes.ContainsKey(Constants.Gender))
model.Gender = attributes[Constants.Gender];
if (attributes.ContainsKey(Constants.Identifier))
model.AppIdentifier = attributes[Constants.Identifier];
No need to do the loop manually in code, you can do it simply with .LastOrDefault():
mymodel.Gender = arrayOfAttrValue
.Where(attr => attr.Attribute == Constants.Gender)
.Select(attr => attr.Value).LastOrDefault() ?? mymodel.Gender;
mymodel.AppIdentifier = arrayOfAttrValue
.Where(attr => attr.Attribute == Constants.Identifier)
.Select(attr => attr.Value).LastOrDefault() ?? mymodel.AppIdentifier;
The ?? mymodel.Gender makes sure we're not setting it to Default(T) (i.e. null) in a situation where it was otherwise set to a value previously. This then matches the functional logic of your initial question.
Doing it this way makes it very clear what you are trying to do. Of course this approach means that you're looping over the array twice, however if your array is actually an array then this performance cost will be very small.
If you still have performance issues with this then you probably want to consider using a better data structure than an arrayOfAttrValue (something that is index accessable by Attribute such as a Dictionary<,>).
I know this is simple, but my mind is playing tricks on me right now. If we have a flat list of objects with the properties GroupSortIndex and ItemSortIndex (within the group) and we want to find the first item in the list, what's the Linq/lambda for that?
About all I can think of is (meta, not literal code...)
var soughtItem = Source.OrderBy(ItemSortIndex).OrderBy(GroupSortIndex).ToList()[0]
...but that just looks so wrong to me for some reason.
Read post : Default Extension methods to get difference between first and firstordefault
you can use FirstOrDefualt() or First() function
var soughtItem = Source.OrderBy(ItemSortIndex).
ThenBy(GroupSortIndex).FirstOrDefualt();
if(soughtItem !=null)//advantage of using firstordefault
{
}
its better to use FirstOrDefualt because if there is no data it will return null intead of excetipn
You can use IOrderedEnumerable.ThenBy (Note: an IOrderedEnumerable is returned from IEnumerable.OrderBy):
var firstItem = source.OrderBy(s => s.GroupSortIndex)
.ThenBy(s => s.ItemSortIndex)
.First();
This orders first by the group and then by the item. You should use FirstOrDefault if the sequence can be empty. Otherwise First raises an exception.
(i've assumed that you want to order first by group and then by the item instead, since the ItemSortIndex is the index of the item within the group(as mentioned))
var soughtItem = Source
.OrderBy(ItemSortIndex)
.ThenBy(GroupSortIndex).First();
If ItemSortIndex and GroupSortIndex are properties instead of functions, then you need:
var soughtItem = Source
.OrderBy(i => ItemSortIndex)
.ThenBy(i => GroupSortIndex).First();
Here's the c# code that I have:
private double get806Fees (Loan loan)
{
Loan.Fee.Items class806;
foreach (Loan.Fee.Item currentFee in loan.Item.Fees)
{
if (currentFee.Classification == 806) class806.Add(currentFee);
}
// then down here I will return the sum of all items in class806
}
Can I do this using linq? If so, how? I have never used linq and i've read in several places that using linq instead of a foreach loop is faster... is this true?
Similar to some existing answers, but doing the projection in the query, to make the Sum call a lot simpler:
var sum = (from fee in loan.Items.Fees
where fee.Classification == 806
select fee.SomeValueToSum).Sum();
loan.Item.Fees.
Where(x => x.Classification == 806).
Sum(x => x.SomeValueProperty)
Whether it is faster or not is debatable. IMO, both complexities are the same, the non-LINQ version may be faster.
var q =
from currentFee in loan.Item.Fees
where currentFee.Classification == 806
select currentFee;
var sum = q.Sum(currentFee => currentFee.Fee);
private double get806Fees(Loan loan)
{
return load.Item.Fees.
Where(f => f.Classification == 806).
Sum(f => f.ValueToCalculateSum);
}
I'm assuming here that ValueToCalculateSum is also a double. If it's not then you have to convert it before it is returned.
All of the answers so far are assuming that you're summing up loan.Fees. But the code you actually posted calls Items.Add() to add each Item in loan.Fees.Items to an Items object, and it's that Items object (and not loan.Fees, which is also an Items object) that you say you want to sum up.
Now, if Items is just a simple collection class, then there's no need to do anything other than what people are suggesting here. But if there's some side-effect of the Add method that we don't know about (or, worse, that you don't know about), simply summing up a filtered list of Item objects might not give you the results you're looking for.
You could still use Linq:
foreach (Loan.Fee.Item currentFee in loan.Item.Fees.Where(x => x.Classification == 806)
{
class806.Add(currentFee);
}
return class806.Sum(x => x.Fee)
I'll confess that I'm a little perplexed by the class hierarchy implied here, though, in which the Loan.Item.Fees property is a collection of Loan.Fee.Item objects. I don't know if what I'm seeing is a namespace hierarchy that conflicts with a class hierarchy, or if you're using nested classes, or what. I know I don't like it.