0

I have this code that will execute Console.WriteLine(x) on every object in what Distinct() will return.

objects.Distinct().ToList().ForEach(x => Console.WriteLine(x));

However, how can I achieve the same thing without using ToList()?

Dmitry Bychenko
  • 180,369
  • 20
  • 160
  • 215
Zwierzak
  • 666
  • 1
  • 11
  • 31

4 Answers4

7

By using foreach:

foreach(var x in objects.Distinct())
     Console.WriteLine(x);

You don't need the list and you don't need the List.ForEach method. Just use a plain loop.

For what it's worth, a ForEach extension method for any kind of sequence.

With this extension method you could use this code:

objects.Distinct().ForEach(Console.WriteLine);
Community
  • 1
  • 1
Tim Schmelter
  • 450,073
  • 74
  • 686
  • 939
1

You don't.

.Distinct() is a method that operates on an IEnumerable, and returns an IEnumerable (lazily evaluated). An IEnumerable is a sequence: it is not a List. Hence, if you want to end up with a list, put the .ToList() at the end.

There is a nice explanation for that here

Community
  • 1
  • 1
brduca
  • 3,573
  • 2
  • 22
  • 30
  • And why this won't work? objects.Distinct().Select( x => Console.WriteLine(x)) – Zwierzak May 24 '16 at 11:06
  • @Zwierzak: Two reasons. The first is that you really need something to be returned from your Select. As it is the compiler will complain about that because it can't work out what type your select lambda is returning (because its not). You could fix that with `.Select(x=>{Console.WriteLine(x); return x;})` but then you'd have the problem with lazy evaluation. You wouldn't actually execute the `Console.WriteLine` until you enumerated the `IEnumerable` that you are creating. This would mean either foreaching or using a `ToList()`... – Chris May 24 '16 at 11:13
  • In other words, trying to make it simple, when you call .Distinc() no action is taken in that moment, it awaits until it's necessary (lazy evaluated), when you call .ToList() the value is evaluated. – brduca May 24 '16 at 11:17
0

Even being ugly, materialization will do (put .ToList() at the end):

objects
  .Distinct()
  .Select(x => {
     Console.WriteLine(x);
     return 1; // <- Select must return something
    })
  .ToList(); // <- this will force Linq to perform Select

A much better approach is just enumerate:

foreach (var x in objects.Distinct())
  Console.WriteLine(x); 
Dmitry Bychenko
  • 180,369
  • 20
  • 160
  • 215
  • Your first code sample gives me an exception, presumably because Console.WriteLine doesn't return anything: 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. – Chris Dunaway May 24 '16 at 14:32
  • @Chris Dunaway: I see I really have to return something, see my edit – Dmitry Bychenko May 24 '16 at 14:43
0

You could achieve this by creating your custom ForEach extension.

Test Program

public static void Main(string[] args)
{
    var objects = new List<DateTime>();

    objects.Add(DateTime.Now.AddDays(1));
    objects.Add(DateTime.Now.AddDays(1));
    objects.Add(DateTime.Now.AddDays(2));
    objects.Add(DateTime.Now.AddDays(2));
    objects.Add(DateTime.Now.AddDays(3));
    objects.Add(DateTime.Now.AddDays(3));
    objects.Add(DateTime.Now.AddDays(4));
    objects.Add(DateTime.Now.AddDays(4));
    objects.Add(DateTime.Now.AddDays(5));
    objects.Add(DateTime.Now.AddDays(5));

    objects.Distinct().ForEach(x => Console.WriteLine(x.ToShortDateString()));
}

Extension

public static class Extensions
{
    public static void ForEach<ST>(this IEnumerable<ST> source, Action<ST> action)
    {
        IEnumerator<ST> enumerator = source.GetEnumerator();
        while (enumerator.MoveNext())
            action(enumerator.Current);
        enumerator.Dispose();
    }

    // OR

    public static void ForEach<ST>(this IEnumerable<ST> source, Action<ST> action)
    {
        foreach (var item in source)
            action(item);
    }
}

PS

Keep in mind that if you are using LINQ-2-SQL, this won't work since linq2sql queries are only evaluated after some specific actions like ToList, AsEnumerable or FirstOrDefault (and some others).

Leandro Soares
  • 2,902
  • 2
  • 27
  • 39
  • 1
    Out of interest why are you explicitly enumerating your source rather than just using `foreach`? – Chris May 24 '16 at 11:15
  • Mostly habits... 2 years ago I've read something about foreach performance and when I've the opportunity I write code with enumerators (When that code won't be seen much). – Leandro Soares May 24 '16 at 11:22
  • 1
    But foreach is better since it automatically disposes if the enumerator is disposable. – Leandro Soares May 24 '16 at 11:23