2

What is the proper ForAll method for the Dictionary object? I am learning the Linq type iteration (method 1 below), but do not getting it to work. The old foreach loop works (method 2 below). Despite many answers on similar questions, e.g. this question, I do not see a Linq iteration solution.

The Dictionary seems to have methods to get all Keys or all Values, usable in the Linq iteration, but not to get all KeyValuePairs. Strange!

I did some fiddling with GetEnumeration(), but without much luck.

The AsParallel() method seemed the solution, found here, but with me I only got a build error: Dictionary does not contain a definition for AsParallel .

One could argue that Method 2 does not have so much more lines of source, but to me the Linq iteration of Method 1 is still more simple, more direct, more elegant.

Is there still an easy way to make Method 1 work?

Version: .Net Framework 4.5.2

using System.Linq;//AsParallel, ToList
using System.Collections.Generic;//Dictionary
using System.Xml.Linq;//XElement
using System.IO;//Path, Dir, File

        public Dictionary<string, string> DictUserConfig = new Dictionary<string, string>();

        public void SaveFile(string FileName)
        {
            XDocument XDConf = new XDocument(new XDeclaration("1.0", "utf-8", "yes"));
#if true//method 1: problem: Dictionary does not have a .ForAll or .ForEach method, or .GetEnumerator().ForAll(x => ...)
            XDConf.Add(
                new XElement("settings",
                    DictUserConfig.ForEach(x =>
                        new XElement("add",
                            new XAttribute("key", x.Key),
                            new XAttribute("value", x.Value)))));
#else//method 2: works
            XElement XSettings = new XElement("settings");
            XDConf.Add(XSettings);
            foreach (KeyValuePair<string, string> x in DictUserConfig)
            {
                XSettings.Add(
                    new XElement("add",
                        new XAttribute("key", x.Key),
                        new XAttribute("value", x.Value)));
            }
#endif
            XDConf.Save(FileName);
            return;
        }

Update: I discovered one problem: missing using System.Linq, removed some time ago when I didn't need that. Discovered by testing the several useful comments on my post.

Now the problem is more easily summarized as:

This works:

DictUserConfig.ToList<KeyValuePair<string, string>>().ForEach(x => Console.WriteLine(x.Key + x.Value));
DictUserConfig.AsParallel().ForAll(x => Console.WriteLine(x.Key + x.Value));

But this does not work:

XElement XE1 =  (XElement)DictUserConfig.ToList<KeyValuePair<string, string>>().ForEach(x => new XElement(x.Key, x.Value));
XElement XE2 =  DictUserConfig.AsParallel().ForEach(x => new XElement(x.Key, x.Value));

And the reason is, I suppose, that ToList and AsParallel do not return the result of the ForEach back to the this. The build error is: cannot convert void to object (or XElement). So the only 'solution' seems to create ForEach myself using the one-liner, which is already showing as a solution, but taking care to return an XElement object.


After testing the final answer, I would like to note that the discussion is not about the relative values of ForEach versus foreach, but that this obscured the real solution of Select. Also, foreach is more the Fortran way of making something happen, and ForEach, and especially Select, rather about what you want to achieve.

I would like to thank everyone for the comments and answers, of which I learned something today.

Community
  • 1
  • 1
Roland
  • 4,619
  • 7
  • 49
  • 81
  • 1
    [This](https://blogs.msdn.microsoft.com/ericlippert/2009/05/18/foreach-vs-foreach/) is a good read. If you still want one you can easily implement it yourself. – Simon Karlsson Feb 22 '16 at 15:10
  • `ForEach` dont work that way. – NoName Feb 22 '16 at 15:13
  • Why it must be LinQ? it slower than normal `foreach` – NoName Feb 22 '16 at 15:14
  • @SimonKarlsson indeed, explains why my question has no solution. But I do not agree, especially with `new Element()` I find ForEach more suitable than foreach. Perhaps matter of taste, but why do we have Linq queries at all? – Roland Feb 22 '16 at 15:19
  • 4
    The `ForEach` is actually not a part of LINQ. – Simon Karlsson Feb 22 '16 at 15:21
  • @Sakura I am not concerned about performance, see no reason why ForEach would be slow. And I am trying to learn something here, otherwise I might as well live with foreach. – Roland Feb 22 '16 at 15:21
  • You mention a build error with `AsParallel` but you do not say what the error is. If you identify what the error is there may be a simple solution. – D Stanley Feb 22 '16 at 15:25
  • You can always spool out a dictionary into a `List` by calling the `ToList()` extension method. – code4life Feb 22 '16 at 15:27
  • 2
    The proper LINQ solution for how to perform an action on each item in a sequence *is to use `foreach`*. Creating a method to do it is *contrary* to the principles of LINQ, which is why there is none, intentionally. Just use a `foreach`, it's the proper solution to this problem. – Servy Feb 22 '16 at 15:31
  • @DStanley I added the build error msg to the question: Dictionary does not have such definition – Roland Feb 22 '16 at 15:32
  • @code4life ToList() : Dictionary does not contain such definition :-( similar to lack of ForEach I suppose – Roland Feb 22 '16 at 15:32
  • Read this: https://blogs.msdn.microsoft.com/ericlippert/2009/05/18/foreach-vs-foreach/ – Maarten Feb 22 '16 at 15:33
  • @SimonKarlsson I did not manage to create the 'simple 1-liner' for that missing method. Would appreciate to learn how – Roland Feb 22 '16 at 15:34
  • Here is perspective: in `foreach` you can get each item and evaluate. in `ForAll` - you modifying actual items – T.S. Feb 22 '16 at 16:30

3 Answers3

3

For each KeyValuePair<string,string> you want to create and return a new XElement with proper attributes. This is called a projection.

Select is LINQ's projection method:

XDConf.Add(new XElement("settings",
    DictUserConfig.Select(
        kvp => new XElement("add", 
            new XAttribute("key", kvp.Key),
            new XAttribute("value", kvp.Value)))/*.
   Cast<object>().ToArray()*/));

The cast at the end is necessary to call the correct constructor of XElement.


Note that List<T>.ForEach iterates over the enumeration and calls an Action<KeyValuePair<TKey,TValue>> for each KeyValuePair. But it does not return anything, so you cannot use it in your XElement constructor call.

René Vogt
  • 43,056
  • 14
  • 77
  • 99
  • This. `Select` *is* the LINQ method for mapping each element x to some element y. – Heinzi Feb 22 '16 at 15:23
  • Why would you spend the cycles to create a projection when you can just call `ToList()`? – code4life Feb 22 '16 at 15:26
  • 1
    @code4life Why would you create a list that's you're just going to immediately throw away, when you could just not do that? – Servy Feb 22 '16 at 15:27
  • 1
    @code4life because `List.ForEach` returns `void` and not an `object[]` as needed for the `XElement(string name, params object[] content)` constructor. – René Vogt Feb 22 '16 at 15:28
  • @Servy: you're not throwing the list away, you're going to iterate through it via `ForEach`. Or am I missing something here? – code4life Feb 22 '16 at 15:28
  • 3
    @code4life you miss that OP wants to return new elements. This _is_ a projection. – René Vogt Feb 22 '16 at 15:29
  • 2
    @code4life The underlying sequence is already iteratable. So you're creating a list and doing nothing with it that you couldn't do with the underlying sequence, thus wasting the effort of creating the list and materializing the entire sequence. – Servy Feb 22 '16 at 15:29
  • @Servy: sorry, I was reading way too fast. I see what you're doing now. But I think it's still worthwhile to provide some educational info on how to use `ToList()` to access `ForEach`. It falls along the principle of "give a man a fish, you feed him for a day, but teach him to fish, and you feed him for a lifetime". – code4life Feb 22 '16 at 15:32
  • @RenéVogt Indeed! I just discovered that myself, and added to the question. List() returns a new list and the return value gets lost. Similar AsParallel(). – Roland Feb 22 '16 at 16:05
  • @RenéVogt Took some discussion at the other answer, but this is the simple solution. The cast at the end seems not needed in my code. – Roland Feb 22 '16 at 17:27
2

You can use the ToList extension method in order to get a list of KeyValuePairs that is compatible with the ForEach method.

Dictionary<string, int> testDict = new Dictionary<string, int>();
testDict.Add("one", 1);
testDict.Add("two", 2);
testDict.Add("three", 3);

testDict.ToList<KeyValuePair<string, int>>().ForEach(x => Console.WriteLine(x.Key));

DotNetFiddle here: https://dotnetfiddle.net/5rRGZc

I would agree with the comment by Sakura that the normal foreach is probably the better choice, though.

Matthew Jaspers
  • 1,546
  • 1
  • 10
  • 13
1

ForEach() is a specific method of List<T> class.

But you can make your own extensor method of Dictionary<TKey, TVakue>:

public static class DictionaryExtensions
{
    public static void ForEach<TKey, TValue>(this Dictionary<TKey, TValue> dict, Action<KeyValuePair<TKey, TValue>> action)
    {
        foreach (var item in dict)
            action(item);
    }
}

And then use it like this:

XElement XSettings = new XElement("settings");
DictUserConfig.ForEach(x =>
    XSettings.Add(new XElement("add",
            new XAttribute("key", (string)x.Key),
            new XAttribute("value", (string)x.Value))));

UPDATE:

Using method 1, then you can use an extensor like this:

public static IEnumerable<TResult> ForEach<TKey, TValue, TResult>(this Dictionary<TKey, TValue> dict, Func<KeyValuePair<TKey, TValue>, TResult> func)
{
    foreach (var item in dict)
        yield return func(item);
}

Then method 1 would be:

XDConf.Add(new XElement("settings",
                DictUserConfig.ForEach(x =>
                    new XElement("add", new XAttribute("key", x.Key),
                                        new XAttribute("value", x.Value))).ToArray()));

Probably a Cast<object>() would be necessary before ToArray().

Arturo Menchaca
  • 15,783
  • 1
  • 29
  • 53
  • 2
    @code4life So everytime the dictionary is iterated it also needs to be converted to a list first, seems efficient. – Simon Karlsson Feb 22 '16 at 15:28
  • @code4life: Yes, I know. it's just to have a foreach to the dictionary itself and not have to convert it each time is required – Arturo Menchaca Feb 22 '16 at 15:30
  • We are getting close now. In the use example both commands should be combined, saving one semicolon, by changing the definition for ForEach with a Func<> instead of the Action<>, returning a List, better: List. I am working on it, but would be glad to accept your updated answer. – Roland Feb 22 '16 at 16:21
  • 1
    @Roland: I updated my answer but I'm not sure if this is what you need. – Arturo Menchaca Feb 22 '16 at 16:45
  • Excellent solution! Exactly what I was originally looking for! I just made a similar solution, but with more code, so I am glad I resisted the temptation to edit your original answer with my solution. Question: is it really necessary to supply TKey, TValue, TResult as arguments? The inner code should already know the types used in the dictionary 'this', and the result type of func? – Roland Feb 22 '16 at 16:59
  • The proposed 'Cast' and the ToArray() are not needed. App works without that. – Roland Feb 22 '16 at 17:01
  • 1
    @Roland: Yes, it is necessary, because in parameters you can not declare generics types, you have to do it in the method definition. It's the syntax of C#. – Arturo Menchaca Feb 22 '16 at 17:04
  • 2
    @Roland: but if this is what you want, i think you can use Select(), does exactly the same as this ForEach() implementation. – Arturo Menchaca Feb 22 '16 at 17:08
  • @ArturoMenchaca As much as I appreciate the code in your Answer, Select() indeed does exactly the same (as I tested), but simpler. Disappointing to discover this after so much trouble, but fun that C# isn't that bad after all. Now I'll have to credit some earlier commenters too. – Roland Feb 22 '16 at 17:22
  • @Roland: My original answer was using Select() but was downvoted so I deleted it thinking it was not what you wanted. – Arturo Menchaca Feb 22 '16 at 17:27
  • @ArturoMenchaca Indeed we arrived at the final solution with a detour. Rene's answer shows a more complete code example. But I upvoted your contributions! – Roland Feb 23 '16 at 10:13