I am trying to gransp wether I can get big refactoring advantages out of learning LINQ.
How can LINQ improve this code, which is a real-world example that is representative for a lot of code in a project I work on:
foreach (SchemeElement elem in mainDiagram.Elements)
{
if (elem.SubType == EElemSubType.BusBar)
{
if (connPts.Busbars.ContainsKey(elem.ConnectionPointId))
{
if (!taAddrList.TAAddressList.ContainsKey(elem.Key))
{
taAddrList.TAAddressList.Add(elem.Key, new TAAddress());
}
taAddrList.TAAddressList[elem.Key] = connPts.Busbars[elem.ConnectionPointId];
}
} // if busbar
} // foreach element
For Clarity:
taAddrList.TAAddressList is of type Dictionary<ElemKey, TAAddress>
where ElemKey is a two-component type that consists of two int ID's.
connPts.Busbars is of type Dictionary<int, TAAddress>
See for yourself:
var query = from element in mainDiagram.Elements
where element.SubType == EElemSubType.BusBar
where connPts.Busbars.ContainsKey(element.ConnectionPointId)
select element;
foreach (var element in query)
{
// by accessing immidiatly in a dictionary (assuming you are using one), you can either insert or update
taAddrList.TAAddressList[element.Key] = connPts.Bushbars[elem.ConnectionPointId];
}
Well this depends, its certainly alot easier to write that sort of stuff in LINQ, but the depends part is on whether TAddressList is just a Dictionary... if it were you can get that dictionary easily:
var dictionary = mainDiagram.Elements.Where(e => e.SubType == EElemSubType.BusBar)
.ToDictionary(k => k.Key, e => connPts.BusBars[e.ConnectionPointId])
If you have to add to TAddressList in exactly the manner you gave in your example, you simply need to ForEach over the list
mainDiagram.Elements.Where(e => e.SubType == EElemSubType.BusBar && !taAddrList.TAAddressList.Contains(e.Key))
.ToList()
.ForEach(e => taAddrList.TAAddressList.Add(elem.Key, connPts.BusBars[e.ConnectionPointId]));
You can use linq for selecting a list of SchemeElement:
var query = from elem in mainDiagram.Elements
where elem.SubType == EElemSubType.BusBar
&& connPts.Busbars.ContainsKey(elem.ConnectionPointId)
select elem;
foreach (SchemeElement elem in query){
if (!taAddrList.TAAddressList.ContainsKey(elem.Key))
{
taAddrList.TAAddressList.Add(elem.Key, new TAAddress());
}
taAddrList.TAAddressList[elem.Key] = connPts.Busbars[elem.ConnectionPointId];
}
var elements =
mainDiagram.Elements
.Where(e => e.SubType == EElemSubType.BusBar &&
connPts.Busbars.ContainsKey(e.ConnectionPointId))
foreach (var elem in elements)
{
if (!taAddrList.TAAddressList.ContainsKey(elem.Key))
{
taAddrList.TAAddressList.Add(elem.Key, new TAAddress());
}
taAddrList.TAAddressList[elem.Key] = connPts.Busbars[elem.ConnectionPointId];
}
var items = mainDiagram.Elements
.Where(el => el.SubType == EElemSubType.BusBar
&& connPts.Busbars.ContainsKey(el.ConnectionPointId));
items.ForEach(item =>
{
if (!taAddrList.TAAddressList.ContainsKey(item.Key))
{
taAddrList.TAAddressList.Add(item.Key, new TAAddress());
}
});
foreach (SchemeElement elem in mainDiagram.Elements.Where(r => r.SubType == EElemSubType.BusBar
&& connPts.Busbars.ContainsKey(r.ConnectionPointId)))
{
if (!taAddrList.TAAddressList.ContainsKey(elem.Key))
{
taAddrList.TAAddressList.Add(elem.Key, new TAAddress());
}
taAddrList.TAAddressList[elem.Key] = connPts.Busbars[elem.ConnectionPointId];
} // foreach element
Code bellow is not tested, I assumed finally addresses want to go a specific Dictionary of Address objects, and address class contains two property, Key and value:
addressDic = mainDiagram.Elements.Where(x=>x.SubType == EElemSubType.BusBar)
.Where(x=>connPts.Busbars.ContainsKey(x.ConnectionPointId))
.GroupBy(x=>x.Key)
.Select(x=>new {Key = x.Key,
Value = connPts.Busbars[x.Last().ConnectionPointId]})
.ToDictionary(x=>x.Key);
but as you can see, It's not very readable in linq, but depend on your power in linq, may be it's simpler than for loop.
Related
I have object and collection in this object.
myObject.myCollection.Where(a => a.Id == Id).FirstOrDefault() = newMyCollection;
Unfortunately this didn't works
If I change single element, then work. For example:
myObject.myCollection.Where(a => a.Id == Id).FirstOrDefault().Id = newMyCollection.Id;
How to update all of object? Thanks for any help
I can do something like this:
myObject.myCollection.Remove(objectOfMyCollection);
myObject.MyCollection.Add(myNewCollection);
But: If my objectOfMyCollection is firstly, then my new object be last.
Try this
var customListItem2 = myObject.myCollection.Where(a => a.Id == Id).FirstOrDefault();
var index = myObject.myCollection.IndexOf(customListItem2);
if(index != -1)
myObject.myCollection[index] = newCustomListItem;
You can just query for a list and iterate over that:
foreach (var obj in myObject.myCollection.Where(a => a.Id == Id).ToList()) {
obj.Id = newMyCollection.Id;
}
To change only first index you can do something like this:
myObject.myCollection.Where(a => a.Id == Id).FirstOrDefault(a => { a.Id = newMyCollection.Id; return true; });
Which in plain English does :
- From myCollection get items Where item's Id is equal to Id
- From selected values get FirstItem and set it's Id to the new one
- Do not check the rest
Or even make it simplier :
myObject.myCollection.FirstOrDefault(a => { if(a.Id == Id) { a.Id = newMyCollection.Id; return true; } return false; });
You would have to replace the underlying references in your list which isn't possible with LINQ (which is designed for querying, not updating objects).
To do this with a list use an old-school for-loop:
for(int i = 0; i < myList.Count; i++)
{
if(myList[i].Id == Id)
{
myList[i] = newElement;
break;
}
}
Using a for- instead of a foreach is necessary here because you re-reference the instances in your list which is not possible using the latter.
Another approach was to create a temporary copy of your list, get the matching element there and it´s index in the list and replace the element at that index in the original list:
var tmp = myList;
foreach(var elem in tmp)
{
if(elem.Id == Id)
{
var index = myList.IndexOf(x => x.Id == Id);
myList[index] = newElement;
break;
}
}
If you want to replicate SQL UPDATE with JOIN and update one or more objects, you can use LINQ as below, this time using CollectionBase-derived objects. But you can use List<T>, etc
(from Type1 o1 in collection1
join Type2 o2 in collection2 on o1.Id equals o2.RelatedId
select new { O1 = o1, O2 = o2 }).
Count(item => (item.O1.SomeProperty = item.O2.SomeOtherProperty) == null);
A little hacky but it acts similar to SQL and does not mutate collections. YeA, it creates light objects that only hold reference to original collection items.
I am struggling with producing a sublist of objects with Linq.
var assignedObject = new List<int>();
foreach (var obj in myObjects) {
if (IsAssigned(obj)) {
assignedObject.add(obj.Id);
}
}
What I want to do now is remove assigned objects from myObjects with Linq .Where(Func<T,bool> predicate).
myObjects = myObject.Where(item => item.Id != assignedObj.All()).ToList();
item.Id != assignedObj.All() is the problem part.
If it is achievable with Linq please educate me.
Otherwise, I am going to implement a loop.
var remainingObjects = new List<MyClass>();
foreach(var obj in myObjects) {
if (!(assignedObjects.IndexOf(obj.Id) > -1)) {
remainingObjects.Add(obj);
}
}
PS: In case you're wondering List<MyClass> myObjects = GetMyObjects(params); which retrieved from somewhere else.
var toRemove = myObjects.Where(item=>assignedObj.Any(a=>a.Id == item.Id)).ToArray()
Does that work?
You can rewrite your All() with a condition, like this:
myObjects = myObject
.Where(item => assignedObj.All(assigned => item.Id != assigned.Id))
.ToList();
However, it is easier to use Contains:
var assignedIds = new HashSet<int>(assignedObject);
myObjects = myObject.Where(item => assignedIds.Contains(item.Id)).ToList();
Using HashSet<int> speeds up the lookup in situations when assignedObject is relatively large - say, hundreds of objects or more.
It can be like this: var list = from item in myObjects where IsAssigned(item) select item
Try this
var assignedObjects = myObjects.Where(m => IsAssigned(m)).ToList()
var remainedObjects = myObjects.Where(m => !IsAssigned(m)).ToList()
Is there any way to reduce the following code into Linq form?
foreach (var current in currentWhiteListApps)
{
var exists = false;
foreach (var whiteList in clientSideWhiteLists)
{
if (current.appID.Equals(whiteList.appID))
{
exists = true;
}
}
if (!exists)
{
deleteList.Add(current);
}
}
All I can think of is:
currentWhiteListApps.Select(x => {
var any = clientSideWhiteLists.Where(y => y.appID.Equals(x.appID));
if (any.Any())
deleteList.AddRange(any.ToArray());
return x;
});
Reason For LINQ
LINQ is far more readable than nested foreach loops, and requires less code. So this is the reason I would like it in LINQ
var deleteList = currentWhiteListApps.Where(x =>
clientSideWhiteLists.All(y => !x.appID.Equals(y.appID)))
.ToList();
var deleteList = currentWhiteListApps.Except(clientSideWhiteLists).ToList();
This solution assumes that both collections contains elements of the same type and this type has overriden Equals() that compares appID.
var validIds = new HashSet<int>(clientSideWhiteLists.Select(x => x.appId));
var deleteList = currentWhiteListApps.Where(x => !validIds.Contains(x.appId)).ToList();
I am seeking any advice or tips on the following method I have that is using LINQ to find a certain property in a Collection that is null and then go through the results (sub-list) and execute a method on another property from the same Collection.
private void SetRaises()
{
if (employeeCollection != null)
{
var noRaiseList = employeeCollection .Where(emp => emp.Raise == null).ToList();
foreach (var record in noRaiseList )
{
CalculateRaise(record);
}
}
}
public void CalculateRaise(Employee emp)
{
if (emp!= null)
emp.Raise = emp.YearsOfService * 100;
}
The part I don't like in the first method, SetRaises(), is the following snippet:
foreach (var record in noRaiseList )
{
CalculateRaise(record);
}
Is there a way to integrate that part into my LINQ expression directly, i.e. some extension method I am not aware of?
Thank you!
The first thing you could do would be: don't generate an intermediate list:
var pending = employeeCollection.Where(emp => emp.Raise == null);
foreach (var record in pending)
{
CalculateRaise(record);
}
which is identical to:
foreach (var record in employeeCollection.Where(emp => emp.Raise == null))
{
CalculateRaise(record);
}
This is now non-buffered deferred execution.
But frankly, the LINQ here isn't giving you much. You could also just:
foreach(var emp in employeeCollection)
{
if(emp.Raise == null) CalculateRaise(emp);
}
If you don't need list of employees without Raise you can do this in one line:
employeeCollection.Where(emp => emp.Raise == null).ToList().ForEach(x => x.Raise = x.YearsOfService * 100);
You could use the ForEach chain-method. But that's only sugar syntax.
employeeCollection.Where(emp => emp.Raise == null)
.ToList()
.ForEach(record => CalculateRaise(record))
It should be something like this:
var noRaiseList = employeeCollection .Where(emp => emp.Raise == null).ToList().ForEach(e=>e.Raise = e.YearsOfService * 100);
I have 2 collections and i have the following code to loop through one collection and see if it exists in another collection. If it does exist, then update a property of that item.
foreach (var favorite in myFavoriteBooks)
{
var book = allBooks.Where(r => r.Name == favorite.Name).FirstOrDefault();
if (book != null)
{
book.IsFavorite = true;
}
}
Is there a more elegant or faster way to achieve this code above?
You can use Join for this, either in extension method syntax or LINQ syntax:
Extension method:
foreach(var favorite in myFavoriteBooks.Join(allBooks,
f => f.Name,
a => a.Name,
(f, a) => a))
{
a.IsFavorite = true;
}
LINQ:
var favorites = from f in myFavoriteBooks
join a in allBooks on f.Name equals a.Name
select a
foreach(var favorite in favorites)
{
favorite.IsFavorite = true;
}
These solutions are functionally identical; they differ only by syntax and they are faster than your original solution, since LINQ will build a hashtable on both sides and use that for matching rather than scanning the other list for every item in the original list.
You can more easily find the books in one collection that match another collection with Any
var booksInCommon = allBooks.Where(b => myFavoriteBooks.Any(bi => bi.Name == b.Name));
foreach(book b in booksInCommon)
b.IsFavorite = true;
Or, if you don't mind "tricky" code
allBooks.Where(b => myFavoriteBooks.Any(bi => bi.Name == b.Name)).ToList()
.ForEach(b => b.IsFavorite = true);
EDIT
As Adam Robinson points out, this is an O(N2) algorithm, so avoid it if you have thousands of books in both of your collections and opt for his Join answer.
If the books collections were stored in dictionaries with the key of Name, then this becomes an easy and very efficient operation:
var myFavoriteBooks = new System.Collections.Generic.Dictionary<string, book>();
var allBooks = new System.Collections.Generic.Dictionary<string, book>();
foreach (var bookName in myFavoriteBooks.Keys)
{
if (allBooks.ContainsKey(bookName))
{
allBooks[bookName].IsFavorite = true;
}
}
var favTitles = new HashSet<string>(favorite.Select(f => f.Name));
//favTitles now a hash-based O(1) name lookup.
foreach(var book in allBooks.Where(b => favTitles.Contains(b.Name)))
book.IsFavorite = true;