I have a problem with LINQ. I have a list of item and I want to change the Parent
value of an orphan item to 0 with the following code:
void Main()
{
var a = new List<Item> {
new Item() { ID = 1, Name = "1", Parent = 0 },
new Item() { ID = 2, Name = "1.1", Parent = 1 },
new Item() { ID = 3, Name = "2", Parent = 0 },
new Item() { ID = 4, Name = "3", Parent = 0 },
new Item() { ID = 5, Name = "1.2", Parent = 1 },
new Item() { ID = 6, Name = "2.1", Parent = 3 },
new Item() { ID = 7, Name = "2.2", Parent = 3 },
new Item() { ID = 8, Name = "3.1", Parent = 4 },
new Item() { ID = 9, Name = "4", Parent = 0 },
new Item() { ID = 10, Name = "5.1", Parent = 11 }
};
Test.Fix(a, x => x.ID, x => x.Parent).Dump();
}
public class Item
{
public long ID {get;set;}
public string Name {get;set;}
public long Parent {get;set;}
}
public static class Test
{
public static List<T> Fix<T>(this List<T> enumeration, Func<T, long> idProperty, Func<T, long> parentProperty)
{
enumeration = enumeration.Select(e =>
{
if (!enumeration.Any(x => idProperty(x) == parentProperty(x))) parentProperty(e) = 0;
return e;
}).ToList();
return enumeration;
}
}
I expected the lasted item with ID = 10
to have Parent = 0
(coz there is no item with ID = 11
) but I got an error:
The left-hand side of an assignment must be a variable, property or indexer
I googled around but still find nothing useful.
Any helps would be appreciated!
Okay, firstly there's nothing dynamic going on here, but as you say, the problem is here:
parentProperty(e) = 0;
parentProperty
is a function, that's all. It's not really a property. It allows you to get from a T
to a long
- and that's all. It could be anything - imagine a Func<string, long>
which returned x.Length * 5
. What would it mean to "set" that to (say) 3?
If you want to be able to set a value, you'll need an action, e.g.
public static List<T> Fix<T>(this List<T> enumeration,
Func<T, long> idProperty,
Func<T, long> parentGetter,
Action<T, long> parentSetter)
{
enumeration = enumeration.Select(e =>
{
long parentId = parentGetter(e);
if (!enumeration.Any(x => idProperty(x) == parentId))
{
parentSetter(e, 0);
}
return e;
}).ToList();
return enumeration;
}
and call it like this:
Test.Fix(a, x => x.ID, x => x.Parent, (x, v) => x.Parent = v).Dump();
Note that I've changed the logic in Any
a bit because your current logic made no sense - it wasn't using e
in the test.
However, this is really not a good use of LINQ - LINQ is designed around not having side-effects. Given that you're only returning the same elements as before, you might as well not return anything, and just use a foreach
loop:
public static void Fix<T>(this List<T> enumeration,
Func<T, long> idProperty,
Func<T, long> parentGetter,
Action<T, long> parentSetter)
{
foreach (T item in enumeration)
{
long parentId = parentGetter(e);
if (!enumeration.Any(x => idProperty(x) == parentId))
{
parentSetter(e, 0);
}
}
}
I would personally then change this to:
public static void Fix<T>(this List<T> enumeration,
Func<T, long> idProperty,
Func<T, long> parentGetter,
Action<T, long> parentSetter)
{
HashSet<long> validIds = new HashSet<long>(enumeration.Select(idProperty));
foreach (T item in enumeration.Where(x => !validIds.Contains(parentGetter(x)))
{
parentSetter(e, 0);
}
}