Changing all values in a lambda/LINQ - c#

I have a dictionary of a list of objects that looks like this
class Client
{
public int Hash { get; set; }
public bool Active { get; set; }
}
var ClientsDict = new Dictionary<string, List<Client>>();
I have no problem setting a single record value in the list
ClientsDict[userId].First(a => a.Hash == hash).Active = true;
How do I do the inverse of that and set all the others in the list to false?
ClientsDict[userId].All().Active = false; //cannot resolve symbol
Note: I wanna avoid a foreach
EDIT:
ClientsDict[userId].ForEach(a => a.Active = false); //this works

If you're mutating things you shouldn't be expressing it as a LINQ transformation. Just use a regular foreach loop:
foreach(var client in ClientsDict[userId])
{
client.Active = false;
}
You can also use the List<T>.ForEach method, but I would advise against it.

Use the Where LINQ method to express the opposite condition and loop over the resulting set:
foreach(var client in ClientsDict[userId].Where(c => c.Hash != hash))
{
client.Active = false;
}
EDIT: the ForEach() method version for comparison:
ClientsDict[userId]
.Where(c => c.Hash != hash)
.ToList()
.ForEach(c => c.Active = false);
I'm usually not a fan of using the ForEach() method, but I have to admit that it doesn't look too bad here. That said, because the method is only available on List<T>, you're forced to call .ToList().
For a more philosophical discussion on whether its use is a good idea or not, see here: “foreach” vs “ForEach”.

Related

Difficulty typing arguments for System.Linq.Enumerable.Select

I've been staring at this for awhile and not sure how to fix it.
All I'm trying to do is fill in the arptable description property where it matches the address in the device table. I am not trying to create another collection from the arptable.
Error:
The type arguments for method 'System.Linq.Enumerable.Select(System.Collections.Generic.IEnumerable, System.Func)' cannot be inferred from the usage. Try specifying the type arguments explicitly.
Here is the offending code:
IEnumerable<ARPTABLE> GetARPTable()
{
IpTable arp = new IpTable();
IEnumerable<ARPTABLE> arptable = arp.GetArpTable();
arptable.ToList().ForEach(i =>
{
DeviceTable.Where(j => j.PhysicalAddress == i.MAC)
.Select(y =>
{
i.Description = y.Model ?? y.DeviceName;
});
});
return arptable;
}
where DeviceTable is
public ObservableCollection<Device> DeviceTable { get; set; }
Thanks for any help (or a better way).
The compiler is having trouble because your lambda expression isn't written correctly and so it fails on type inference. Unfortunately, the whole construction of the method is broken and I'm not sure I really understand what you're trying to accomplish here.
But in terms of getting it to compile, your method should look more like this:
IEnumerable<ARPTABLE> GetARPTable()
{
IpTable arp = new IpTable();
IEnumerable<ARPTABLE> arptable = arp.GetArpTable();
foreach (var i in arptable)
{
Device j = DeviceTable.FirstOrDefault(j => j.PhysicalAddress == i.MAC);
if (j != null)
{
i.Description = j.Model ?? j.DeviceName;
}
}
return arptable;
}
As a general note: do not use methods like Select() as a way to simply visit each element in the collection. The expression you give to the Select() method will not actually be evaluated unless you evaluate the IEnumerable<T> that was returned, which you did not in this case. And even if you do evaluate the IEnumerable<T>, it's an inefficient misuse of the method.
Also, while List<T>.ForEach() could be considered convenient by some, it is wasteful to convert an IEnumerable<T> to a List<T> just for the purpose of calling that method. A regular foreach statement works fine and is more efficient.
LINQ is not ment to be used for filling in data. It's a query language and so the Select method returns a new sequence. Just do it with foreach. I would image it could look like this, although I'm not exactly sure if I got the logic right.
foreach(var table in arptable)
{
var device = DeviceTable.SingleOrDefault(...);
if (device != null)
{
table.Description = device.Model ?? device.DeviceName;
}
}
As for your current form
arptable.ToList().ForEach(i =>
this is really not necessary, why cast the sequence to list if you don't have to? Just to use that ForEach? We can do better.
DeviceTable.Where(j => j.PhysicalAddress == i.MAC)
.Select(y => i.Description = y.Model ?? y.DeviceName);
This returns a new sequence, which you are not storing in any local variable. LINQ queries should not have side effect, it's against the lambda calculus, the idea behind LINQ itself.
i like the other answers. if you still want to use linq, this is how you would:
IEnumerable<ARPTABLE> GetARPTable()
{
IpTable arp = new IpTable();
IEnumerable<ARPTABLE> arptable = arp.GetArpTable();
arptable = arptable.Select(i =>
{
Device device = DeviceTable.SingleOrDefault(j => j.PhysicalAddress == i.MAC);
if (device != null)
{
i.Description = device.Model ?? device.DeviceName;
}
return i;
});
return arptable;
}

Linq Method Error IOrderedQueryable

I have a database with a specific id with recorded Time's, I need help on trying to figure out time gap's between an ID's time's e.g 13:05:15 and 13:05:45 though if the time gap is over 10/15 seconds it needs to be recorded so it can be used in say a text file/other data etc. I previously asked a similar question on here, here is what my code looks like so far:
This class is used to manipulate data through the linq var being queried/looped
public class Result
{
public bool LongerThan10Seconds { get; set; }
public int Id { get; set; }
public DateTime CompletionTime { get; set; }
}
This is the foor loop within a separate class which was my original idea
using (var data = new ProjectEntities())
{
Result lastResult = null;
List<Result> dataResults = new List<Result>();
foreach(var subResult in data.Status.Select(x => x.ID).Distinct().Select(Id => data.Status.Where(x => x.ID == Id).OrderBy(x => x.Time)))
{
if (lastResult != null)
{
if (subResult.CompletionTime.Subtract(lastResult.CompletionTime).Seconds > 10)
dataResults.Add(subResult);
}
lastResult = subResult;
}
Which I got the error:
Linq.IOrderedQueryAble does not contain a definition for 'CompletionTime' and no Extension method 'CompletionTime' accepting a first argument of type 'System.Linq.IOrderedQueryable.
I changed the for loop to use an object of the manipulation class
foreach(Result subResult in data.AssetStatusHistories.Select(x => x.ID).Distinct().SelectMany(Id => data.AssetStatusHistories.Where(x => x.ID == Id).OrderBy(x => x.TimeStamp)))
{
if (lastResult != null)
{
if (subResult.CompletionTime.Subtract(lastResult.CompletionTime).Seconds > 10)
{
vehicleResults.Add(subResult);
}
}
lastResult = subResult;
}
Though now I get the error: Cannot convert type 'Project.Status' to 'Project.Result'
Does anyone possibly have a solution to get around this I have looked through a few resources but haven't been able to find anything of helps also even on Microsoft's Linq Forum. Any help is much appreciated ! :)
Try adding .ToList() to the end of your LINQ statement, after OrderBy:
var results = data.Status.Select(x => x.ID).Distinct()
.Select(Id => data.Status.Where(x => x.ID == Id)
.OrderBy(x => x.Time)
.ToList();
foreach(var subResult in results))
{
...
}
Also, I think you could modify your LINQ to do a GroupBy of the ID column, but that's something you could do research on if you wish. (Tutorial)
Your linq query (in the second try) will return an IEnumerable of whatever is the element type of data.AssetStatusHistories. I assume this is some kind of IEnumerable<Project.Status>, so you're in fact trying to assign Project.Status objects to an iterator variable (subResult) of type Result, which is why you're getting the error Cannot convert type 'Project.Status' to 'Project.Result'.
So your problem is not really in the linq query, you just need a way to convert your Project.Status objects to Project.Result objects.

C# Linq help improve performance?

Excuse my pseudo code below. I'm pretty sure there is a magical way to write this in a single linq statement that will also dramatically improve the performance. Here I have a list of millions of records in AList. The id may not be unique. What I'm after is the original list removing all duplicates (based on the id), but always grabbing the record with the earliest date. mystring is almost always a different value when there is a duplicate id.
public class A
{
public string id { get; set; }
public string mystring { get; set; }
public DateTime mydate { get; set; }
}
List<A> aListNew = new List<A>();
foreach (var v in AList)
{
var first = AList.Where(d => d.id == v.id).OrderBy(d => d.mydate).First();
// If not already added, then we add
if (!aListNew.Where(t => t.id == first.id).Any())
aListNew.Add(first);
}
You could use grouping directly to accomplish this in one LINQ statement:
List<A> aListNew = AList
.GroupBy(d => d.id)
.Select(g => g.OrderBy(i => i.mydate).First())
.ToList();
The fastest is probably going to be a straight foreach loop with a dictionary:
Dictionary<int, A> lookup = Dictionary<int, A>();
foreach (var v in AList)
{
if(!lookup.ContainsKey(v.id))
// add it
lookup[id] = v;
else if (lookup[id].mydate > v.mydate)
// replace it
lookup[id] = v;
}
// convert to list
List<A> aListNew = lookup.Values.ToList();
A Linq GroupBy / First() query might be comparable if there are few collisions, but either one is going to be O(N) since it has to traverse the whole list.
This should be easiest. No LINQ involved anyway.
var lookup = Dictionary<int, A>();
foreach(var a in aListNew.OrderByDescending(d => d.mydate)) {
lookup[a.id] = a;
}
var result = lookup.Values.ToList();
Note that sub-LINQ will hurt performance, and that's why I choose not to use it. Remember that LINQ is there to make your task easier, not to make the execution faster.

How to modify only one or two field(s) in LINQ projections?

I have this LINQ query:
List<Customers> customers = customerManager.GetCustomers();
return customers.Select(i => new Customer {
FullName = i.FullName,
Birthday = i.Birthday,
Score = i.Score,
// Here, I've got more fields to fill
IsVip = DetermineVip(i.Score)
}).ToList();
In other words, I only want one or two fields of the list of the customers to be modified based on a condition, in my business method. I've got two ways to do this,
Using for...each loop, to loop over customers and modify that field (imperative approach)
Using LINQ projection (declarative approach)
Is there any technique to be used in LINQ query, to only modify one property in projection? For example, something like:
return customers.Select(i => new Customer {
result = i // telling LINQ to fill other properties as it is
IsVip = DetermineVip(i.Score) // then modifying this one property
}).ToList();
you can use
return customers.Select(i => {
i.IsVip = DetermineVip(i.Score);
return i;
}).ToList();
Contrary to other answers, you can modify the source content within linq by calling a method in the Select statement (note that this is not supported by EF although that shouldn't be a problem for you).
return customers.Select(customer =>
{
customer.FullName = "foo";
return customer;
});
You "can", if you create a copy constructor, which initializes a new object with the values of an existing object:
partial class Customer
{
public Customer(Customer original)
{
this.FullName = original.FullName;
//...
}
}
Then you can do:
return customers.Select(i => new Customer(i) { IsVip = DetermineVip(i.Score)})
.ToList()
But the downfall here is you will be creating a new Customer object based on each existing object, and not modifying the existing object - this is why I have put "can" in quotes. I do not know if this is truly what you desire.
No, Linq was designed to iterate over collections without affecting the contents of the source enumerable.
You can however create your own method for iterating and mutating the collection:
public static void ForEach<T>(this IEnumerable<T> enumeration, Action<T> action)
{
foreach(T item in enumeration)
{
action(item);
}
}
You can then use as follows:
return customers.ToList()
.ForEach(i => i.IsVip = DetermineVip(i.Score))
.ToList();
Note that the first ForEach will clone the source list.
As customers already is a List, you can use the ForEach method:
customers.ForEach(c => c.IsVip = DetermineVip(c.Score));

My linq select does not work, my foreach does

I have the following LINQ Select which does not work.
Data.Select(d => d.Value.IsDirty = true); //-- Not working
My longer workaround does.
foreach (var d in Data)
d.Value.IsDirty = true;
Why does my first code not work?
Projection functions like Select, Where, etc. define queries. Simply calling Select does not actually do anything until the query is evaluated (almost certainly, at some point, by a foreach).
If you were to do something to force execution of the query (calling Count, for instance), you'd see it take effect.
It is, however, a bit of an abuse. These functions aren't specifically intended for state-altering operations.
Select() returns an IEnumerable<…>, which has the ability to iterate over the input and invoke the code in question, but doesn't actually do so until you enumerate it in some manner:
Data.Select(d => d.Value.IsDirty = true).ToList();
or
foreach (var _ in Data.Select(d => d.Value.IsDirty = true))
; // Do nothing
However, given that they perform side effects (obviously the intent here), both of the above are bad karma. Don't use them. Your original foreach is the only sensible choice.
Calling Select does not cause the side effect you need, even if you force execution by iterating over the elements.
If you want the side effect you have to do foreach.
For example:
class MyValue
{
public MyValue(bool flag) { Flag = flag; }
public bool Flag { get; set; }
}
class MyValueContainer
{
public MyValueContainer(MyValue val) { MyVal = val; }
public MyValue MyVal { get; set; }
}
class Program
{
static void Main(string[] args)
{
var someList = new List<MyValueContainer>();
someList.Add(new MyValueContainer(new MyValue(true)));
someList.Add(new MyValueContainer(new MyValue(true)));
someList.Add(new MyValueContainer(new MyValue(false)));
someList.Add(new MyValueContainer(new MyValue(true)));
var trueCount = someList.Count(x => x.MyVal.Flag); // 3
var falseCount = someList.Count(x => !x.MyVal.Flag); // 1
// try causing side effect by calling Select
someList.Select(x => x.MyVal.Flag = false);
// force execution. call Count
trueCount = someList.Count(x => x.MyVal.Flag); // still 3... no side effect.
falseCount = someList.Count(x => !x.MyVal.Flag); // still 1... no side effect.
foreach (var x in someList)
x.MyVal.Flag = false;
trueCount = someList.Count(x => x.MyVal.Flag); // 0... side effect seen.
falseCount = someList.Count(x => !x.MyVal.Flag); // 4... side effect seen.
}
}

Categories

Resources