I’ve written the following code to set the properties on various classes. It works, but one of my new year’s rsolutions is to make as much use of LINQ as possible and obviously this code doesn’t. Is there a way to rewrite it in a “pure LINQ” format, preferably without using the foreach loops? (Even better if it can be done in a single LINQ statement – substatements are fine.)
I tried playing around with join but that didn’t get me anywhere, hence I’m asking for an answer to this question – preferably without an explanation, as I’d prefer to “decompile” the solution to figure out how it works. (As you can probably guess I’m currently a lot better at reading LINQ than writing it, but I intend to change that…)
public void PopulateBlueprints(IEnumerable<Blueprint> blueprints)
{
XElement items = GetItems();
// item id => name mappings
var itemsDictionary = (
from item in items
select new
{
Id = Convert.ToUInt32(item.Attribute("id").Value),
Name = item.Attribute("name").Value,
}).Distinct().ToDictionary(pair => pair.Id, pair => pair.Name);
foreach (var blueprint in blueprints)
{
foreach (var material in blueprint.Input.Keys)
{
if (itemsDictionary.ContainsKey(material.Id))
{
material.Name = itemsDictionary[material.Id];
}
else
{
Console.WriteLine("m: " + material.Id);
}
}
if (itemsDictionary.ContainsKey(blueprint.Output.Id))
{
blueprint.Output.Name = itemsDictionary[blueprint.Output.Id];
}
else
{
Console.WriteLine("b: " + blueprint.Output.Id);
}
}
}
Definition of the requisite classes follow; they are merely containers for data and I’ve stripped out all the bits irrelevant to my question:
public class Material
{
public uint Id { get; set; }
public string Name { get; set; }
}
public class Product
{
public uint Id { get; set; }
public string Name { get; set; }
}
public class Blueprint
{
public IDictionary<Material, uint> Input { get; set; }
public Product Output { get; set; }
}
I don’t think this is actually a good candidate for conversion to LINQ – at least not in its current form.
Yes, you have a nested foreach loop – but you’re doing something else in the top-level foreach loop, so it’s not the easy-to-convert form which just contains nesting.
More importantly, the body of your code is all about side-effects, whether that’s writing to the console or changing the values within the objects you’ve found. LINQ is great when you’ve got a complicated query and you want to loop over that to act on each item in turn, possibly with side-effects… but your queries aren’t really complicated, so you wouldn’t get much benefit.
One thing you could do is give
BlueprintandProducta common interface containingIdandName. Then you could write a single method to update the products and blueprints viaitemsDictionarybased on a query for each:This is assuming you don’t actually need the console output. If you do, you could always pass in the appropriate prefix and add an “else” block in the method. Note that I’ve used
TryGetValueinstead of performing two lookups on the dictionary for each iteration.