LINQ equivalent of foreach for IEnumerable

后端 未结 22 2154
夕颜
夕颜 2020-11-21 22:54

I\'d like to do the equivalent of the following in LINQ, but I can\'t figure out how:

IEnumerable items = GetItems();
items.ForEach(i => i.DoS         


        
相关标签:
22条回答
  • 2020-11-21 23:33

    Update 7/17/2012: Apparently as of C# 5.0, the behavior of foreach described below has been changed and "the use of a foreach iteration variable in a nested lambda expression no longer produces unexpected results." This answer does not apply to C# ≥ 5.0.

    @John Skeet and everyone who prefers the foreach keyword.

    The problem with "foreach" in C# prior to 5.0, is that it is inconsistent with how the equivalent "for comprehension" works in other languages, and with how I would expect it to work (personal opinion stated here only because others have mentioned their opinion regarding readability). See all of the questions concerning "Access to modified closure" as well as "Closing over the loop variable considered harmful". This is only "harmful" because of the way "foreach" is implemented in C#.

    Take the following examples using the functionally equivalent extension method to that in @Fredrik Kalseth's answer.

    public static class Enumerables
    {
        public static void ForEach<T>(this IEnumerable<T> @this, Action<T> action)
        {
            foreach (T item in @this)
            {
                action(item);
            }
        }
    }
    

    Apologies for the overly contrived example. I'm only using Observable because it's not entirely far fetched to do something like this. Obviously there are better ways to create this observable, I am only attempting to demonstrate a point. Typically the code subscribed to the observable is executed asynchronously and potentially in another thread. If using "foreach", this could produce very strange and potentially non-deterministic results.

    The following test using "ForEach" extension method passes:

    [Test]
    public void ForEachExtensionWin()
    {
        //Yes, I know there is an Observable.Range.
        var values = Enumerable.Range(0, 10);
    
        var observable = Observable.Create<Func<int>>(source =>
                                {
                                    values.ForEach(value => 
                                        source.OnNext(() => value));
    
                                    source.OnCompleted();
                                    return () => { };
                                });
    
        //Simulate subscribing and evaluating Funcs
        var evaluatedObservable = observable.ToEnumerable().Select(func => func()).ToList();
    
        //Win
        Assert.That(evaluatedObservable, 
            Is.EquivalentTo(values.ToList()));
    }
    

    The following fails with the error:

    Expected: equivalent to < 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 > But was: < 9, 9, 9, 9, 9, 9, 9, 9, 9, 9 >

    [Test]
    public void ForEachKeywordFail()
    {
        //Yes, I know there is an Observable.Range.
        var values = Enumerable.Range(0, 10);
    
        var observable = Observable.Create<Func<int>>(source =>
                                {
                                    foreach (var value in values)
                                    {
                                        //If you have resharper, notice the warning
                                        source.OnNext(() => value);
                                    }
                                    source.OnCompleted();
                                    return () => { };
                                });
    
        //Simulate subscribing and evaluating Funcs
        var evaluatedObservable = observable.ToEnumerable().Select(func => func()).ToList();
    
        //Fail
        Assert.That(evaluatedObservable, 
            Is.EquivalentTo(values.ToList()));
    }
    
    0 讨论(0)
  • 2020-11-21 23:33

    I took Fredrik's method and modified the return type.

    This way, the method supports deferred execution like other LINQ methods.

    EDIT: If this wasn't clear, any usage of this method must end with ToList() or any other way to force the method to work on the complete enumerable. Otherwise, the action would not be performed!

    public static IEnumerable<T> ForEach<T>(this IEnumerable<T> enumeration, Action<T> action)
    {
        foreach (T item in enumeration)
        {
            action(item);
            yield return item;
        }
    }
    

    And here's the test to help see it:

    [Test]
    public void TestDefferedExecutionOfIEnumerableForEach()
    {
        IEnumerable<char> enumerable = new[] {'a', 'b', 'c'};
    
        var sb = new StringBuilder();
    
        enumerable
            .ForEach(c => sb.Append("1"))
            .ForEach(c => sb.Append("2"))
            .ToList();
    
        Assert.That(sb.ToString(), Is.EqualTo("121212"));
    }
    

    If you remove the ToList() in the end, you will see the test failing since the StringBuilder contains an empty string. This is because no method forced the ForEach to enumerate.

    0 讨论(0)
  • 2020-11-21 23:34

    Now we have the option of...

            ParallelOptions parallelOptions = new ParallelOptions();
            parallelOptions.MaxDegreeOfParallelism = 4;
    #if DEBUG
            parallelOptions.MaxDegreeOfParallelism = 1;
    #endif
            Parallel.ForEach(bookIdList, parallelOptions, bookID => UpdateStockCount(bookID));
    

    Of course, this opens up a whole new can of threadworms.

    ps (Sorry about the fonts, it's what the system decided)

    0 讨论(0)
  • 2020-11-21 23:39

    For VB.NET you should use:

    listVariable.ForEach(Sub(i) i.Property = "Value")
    
    0 讨论(0)
  • 2020-11-21 23:42

    Inspired by Jon Skeet, I have extended his solution with the following:

    Extension Method:

    public static void Execute<TSource, TKey>(this IEnumerable<TSource> source, Action<TKey> applyBehavior, Func<TSource, TKey> keySelector)
    {
        foreach (var item in source)
        {
            var target = keySelector(item);
            applyBehavior(target);
        }
    }
    

    Client:

    var jobs = new List<Job>() 
        { 
            new Job { Id = "XAML Developer" }, 
            new Job { Id = "Assassin" }, 
            new Job { Id = "Narco Trafficker" }
        };
    
    jobs.Execute(ApplyFilter, j => j.Id);
    

    . . .

        public void ApplyFilter(string filterId)
        {
            Debug.WriteLine(filterId);
        }
    
    0 讨论(0)
  • 2020-11-21 23:42

    I respectually disagree with the notion that link extension methods should be side-effect free (not only because they aren't, any delegate can perform side effects).

    Consider the following:

       public class Element {}
    
       public Enum ProcessType
       {
          This = 0, That = 1, SomethingElse = 2
       }
    
       public class Class1
       {
          private Dictionary<ProcessType, Action<Element>> actions = 
             new Dictionary<ProcessType,Action<Element>>();
    
          public Class1()
          {
             actions.Add( ProcessType.This, DoThis );
             actions.Add( ProcessType.That, DoThat );
             actions.Add( ProcessType.SomethingElse, DoSomethingElse );
          }
    
          // Element actions:
    
          // This example defines 3 distict actions
          // that can be applied to individual elements,
          // But for the sake of the argument, make
          // no assumption about how many distict
          // actions there may, and that there could
          // possibly be many more.
    
          public void DoThis( Element element )
          {
             // Do something to element
          }
    
          public void DoThat( Element element )
          {
             // Do something to element
          }
    
          public void DoSomethingElse( Element element )
          {
             // Do something to element
          }
    
          public void Apply( ProcessType processType, IEnumerable<Element> elements )
          {
             Action<Element> action = null;
             if( ! actions.TryGetValue( processType, out action ) )
                throw new ArgumentException("processType");
             foreach( element in elements ) 
                action(element);
          }
       }
    

    What the example shows is really just a kind of late-binding that allows one invoke one of many possible actions having side-effects on a sequence of elements, without having to write a big switch construct to decode the value that defines the action and translate it into its corresponding method.

    0 讨论(0)
提交回复
热议问题