3

I have this working code:

using System;
using System.IO;
using System.Collections.Generic;
using System.Linq;

public class Example {
    public static void Main(string[] args) {
        var files = new Dictionary<string, Dictionary<string, int>>()
                   { { "file1", new Dictionary<string, int>() { { "A", 1 } } } };
        foreach(var file in files) {
            File.WriteAllLines(file.Key + ".txt", file.Value.Select(
                    item => item.Key + item.Value.ToString("000")).ToArray());
        }
    }
}

But I want to change the foreach to LINQ syntax. Nothing I already tried worked.

Maniero
  • 10,311
  • 6
  • 40
  • 85
  • 1
    Yor question title and body are not related. More information is needed if you wish someone to help you. For example show statement where nested dictionary cannot be infered – Edin May 24 '14 at 17:13
  • @Edin Sorry, I change the question when I was writing because what I gave tried certainly was very wrong. – Maniero May 24 '14 at 17:15
  • 2
    For each is appropriate here - your code is strictly side effect based which is generally not what you use functional methods for. – Alexei Levenkov May 24 '14 at 17:16
  • @AlexeiLevenkov I understand that, but I'm trying to learn how to do in other ways. – Maniero May 24 '14 at 17:24
  • 1
    @bigown trying to use LINQ here is learning how to do this in *wrong* way. You already have a collection, and now you want to perform some action for each item in that collection. That's what `foreach` loop is for. – MarcinJuraszek May 24 '14 at 17:52
  • @MarcinJuraszek Learn the wrong way is learning too. – Maniero May 24 '14 at 17:57

2 Answers2

2

Is this what you are after?

var files = new Dictionary<string, Dictionary<string, int>>() 
            { { "file1", new Dictionary<string, int>() { { "A", 1 } } } };
files.ForEach(kvp =>
    File.WriteAllLines(kvp.Key + ".txt", kvp.Value.Select(
            item => item.Key + item.Value.ToString("000")).ToArray()));

As per Alexei's comment, IEnumerable.ForEach isn't a standard extension method as it implies mutation, which isn't the aim of functional programming. You can add it with a helper method like this one:

public static void ForEach<T>(
    this IEnumerable<T> source,
    Action<T> action)
{
    foreach (T element in source)
        action(element);
}    

Also, your original title implied that the initializer syntax for Dictionaries is unwieldy. What you can do to reduce the amount of typing / code real estate for a large number of elements is to build up an array of anonymous objects and then ToDictionary(). Unfortunately there is a small performance impact:

var files = new [] { new { key = "file1", 
                           value = new [] { new {key = "A", value = 1 } } } }
    .ToDictionary(
        _ => _.key, 
        _ => _.value.ToDictionary(x => x.key, x => x.value));
Community
  • 1
  • 1
StuartLC
  • 104,537
  • 17
  • 209
  • 285
  • 1
    Thanks, I have tried something like that without success. Your code generate this compiler error: `select.cs(12,19): error CS1061: 'System.Collections.Generic.Dictionary>' does not contain a definition for 'ForEach' and no extension method 'ForEach' accepting a first argument of type 'System.Collections.Generic.Dictionary>' could be found (are you missing a using directive or an assembly reference?)` – Maniero May 24 '14 at 17:20
  • Apols - you need to roll your own extension method. I've updated. – StuartLC May 24 '14 at 17:26
  • I'm playing with that now, but I think this is the way. I probably will accept it later. – Maniero May 24 '14 at 17:32
  • There is no difference between `foreach` and `ForEach` here... I can't see how it makes the solution better. And btw. there is already `ForEach` method defined on `List`. – MarcinJuraszek May 24 '14 at 17:49
  • 1
    @MarcinJuraszek OP is iterating a dictionary – StuartLC May 24 '14 at 17:50
  • It's not about what kind of collection OP's iteration over. It's about the fact, that writing your own `ForEach` extension method on `IEnumerable` is imo not a good idea and should not be done. – MarcinJuraszek May 24 '14 at 17:59
2

foreach is exactly what you should be using here. LINQ is all about querying data: projecting, filtering, sorting, grouping, etc. You're trying to execute an action for each element in collection which is already in there as you it.

Just iterate using foreach.

There are reasons why there is no ForEach extension method on IEnumerable<T>:

It's mostly about:

The reason to not use ForEach is that it blurs the boundary between pure functional code and state-full imperative code.

The only reason I can see not to use foreach loop is when you want to make your actions run in parallel by using Parallel.ForEach instead:

Parallel.ForEach(
    files,
    kvp => File.WriteAllLines(kvp.Key + ".txt", kvp.Value.Select(
               item => item.Key + item.Value.ToString("000")).ToArray()));

Having ForEach extension method on IEnumerable<T> is a bad design and I advice against it.

Community
  • 1
  • 1
MarcinJuraszek
  • 124,003
  • 15
  • 196
  • 263