IEnumerable 方法管道

The IEnumerable appraoch pipeline

代码来自 https://ayende.com/blog/3082/pipes-and-filters-the-ienumerable-appraoch

问题 注册了 3 个操作。 首先获取系统中的所有进程。 第二个过滤过程。 第三写进程名称。

但是使用了yield和GetEnumerator。

current = operation.Execute(current);被执行了三次,第一次操作的输出列表是第二次操作的输入。它只是一个列表(操作)。 我不明白 private readonly List<IOperation<T>> operations = new List<IOperation<T>>(); 中存储的内容和方式???因为 foreach 有三个执行。执行 enumerator.MoveNext() 时执行的某种委托?好的,但是我们有 Foreach,它被执行了吗?在使用 GetEnumerator 之前三次。 所以列表“操作”应该是 ovverriden ...? :) 我想了解一种机制。在使用 enumerator.MoveNext() 执行之前如何存储此“委托”。它的用途是什么?

class Program
    {
        static void Main(string[] args)
        {
            var trivialProcess = new Pipeline<Process>();

            trivialProcess.Register(new GetAllProcesses());
            trivialProcess.Register(new LimitByWorkingSetSize());
            trivialProcess.Register(new PrintProcessName());

            trivialProcess.Execute();
        }
    }


    interface IOperation<T>
    {
        IEnumerable<T> Execute(IEnumerable<T> input);

    }

    class GetAllProcesses : IOperation<Process>
    {
        public IEnumerable<Process> Execute(IEnumerable<Process> input)
        {
            Debug.WriteLine("GetAllProcesses Execute");
            return Process.GetProcesses();
        }
    }

    class LimitByWorkingSetSize : IOperation<Process>
    {
        public IEnumerable<Process> Execute(IEnumerable<Process> input)
        {
            int maxSizeBytes = 50 * 1024 * 1024;
            Debug.WriteLine("LimitByWorkingSetSize Enter");
            foreach (Process process in input)
            {
                Debug.WriteLine("LimitByWorkingSetSize foreach");
                if (process.WorkingSet64 > maxSizeBytes)
                {
                    Debug.WriteLine("LimitByWorkingSetSize yield");
                    yield return process;
                }
            }

        }
    }

    class PrintProcessName : IOperation<Process>
    {
        public IEnumerable<Process> Execute(IEnumerable<Process> input)
        {
            Debug.WriteLine("PrintProcessName Enter");
            foreach (Process process in input)
            {
                Debug.WriteLine("PrintProcessName  print");
                Console.WriteLine(process.ProcessName);
            }
            Debug.WriteLine("PrintProcessName break");
            yield break;
        }
    }

    class Pipeline<T>
    {
        private readonly List<IOperation<T>> operations = new List<IOperation<T>>();

        public Pipeline<T> Register(IOperation<T> operation)
        {
            operations.Add(operation);
            return this;
        }

        public void Execute()
        {
            IEnumerable<T> current = new List<T>();

            foreach (IOperation<T> operation in operations)
            {
                current = operation.Execute(current);
            }
            IEnumerator<T> enumerator = current.GetEnumerator();
            while (enumerator.MoveNext()) ;
        }
    }

I dont understand How and what is stored in private readonly List<IOperation> operations = new List<IOperation>(); ???

它是 IOperation<T> 的列表,在本例中是 GetAllProcessesLimitByWorkingSetSizePrintProcessName 类 的实例。所有这些 类 都实现了 IOperation<T> 接口,因此能够被视为 IOperation<T>,并允许存储在列表中。

as foreach has three execution

是的,但这只是为 LimitByPrintProcesses 类 建立了调用链,因为它们的 Execute 是生成器(特征 yield ) - 它们只会在被枚举时执行。 current = operation.Execute(current); 将立即执行 GetAllExecute,因为那不会 yield 任何东西(但它可以修改,所以它做了)但是 运行依赖于 LimitBy 的 PrintProcess 中的代码仅在 MoveNext() 发生时发生。 PrintProcess 捕获控制流并且在枚举时不 yield 任何东西。

delegate which is executed when enumerator.MoveNext() is executed?

我怀疑这对这种情况下的收益率机制进行了深入研究(但你在下面再次询问它,所以..)

OK, but we have Foreach, which is executed?

给出的代码示例中有很多 foreaches。执行 PrintProcess 的 Execute 运行一个 foreach,它枚举链中下一个方法的输出(LimitByExecute)——它本身也包含一个 foreach。如果你在调试器中单步执行它,在每个 foreach 下的 { 上设置一个断点,你会看到它在 PrintProcessLimitBy 之间来回跳转; LimitByPrintProcess 枚举的某些循环过程中生成数据。 PrintProcesses 对 further-up 操作没有 yield 任何东西,因此控制权在这两者之间来回传递,直到 LimitBy 没有任何东西可以提供,此时将没有PrintProcess 枚举的更多内容

So list "operations " should be ovverriden ... ?

我怀疑这是覆盖的错字。代码中没有任何内容可以覆盖 operations 列表; current 被覆盖了。它只是作为一种记住上次输出的方式,因此这次它可以在循环的每一次传递中成为输入。一开始就没有必要将它初始化为一个新的 List,因为这个链中的第一个 Execute 没有对其 input

做任何事情

How this "delegates" is stored before execution enumerator.MoveNext() is used. and at what it is used?

我认为您实际上是在问 yield 是如何工作的;简短的简短版本是编译器为您编写一些 类 来实现一个枚举器,该枚举器根据您在 yield 关键字周围使用的逻辑生成值。冗长的答案相当可怕,不是你想自己写的东西,这就是为什么我们通常很乐意让编译器生成它而不是在引擎盖下查看:

#define DEBUG
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Security;
using System.Security.Permissions;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("0.0.0.0")]
[module: UnverifiableCode]
namespace ConsoleApp7net5
{
    internal class Program
    {
        private static void Main(string[] args)
        {
            Pipeline<Process> pipeline = new Pipeline<Process>();
            pipeline.Register(new GetAllProcesses());
            pipeline.Register(new LimitByWorkingSetSize());
            pipeline.Register(new PrintProcessName());
            pipeline.Execute();
        }
    }
    internal interface IOperation<T>
    {
        IEnumerable<T> Execute(IEnumerable<T> input);
    }
    internal class GetAllProcesses : IOperation<Process>
    {
        public IEnumerable<Process> Execute(IEnumerable<Process> input)
        {
            Debug.WriteLine("GetAllProcesses Execute");
            return Process.GetProcesses();
        }
    }
    internal class LimitByWorkingSetSize : IOperation<Process>
    {
        [CompilerGenerated]
        private sealed class <Execute>d__0 : IEnumerable<Process>, IEnumerable, IEnumerator<Process>, IDisposable, IEnumerator
        {
            private int <>1__state;

            private Process <>2__current;

            private int <>l__initialThreadId;

            private IEnumerable<Process> input;

            public IEnumerable<Process> <>3__input;

            public LimitByWorkingSetSize <>4__this;

            private int <maxSizeBytes>5__1;

            private IEnumerator<Process> <>s__2;

            private Process <process>5__3;

            Process IEnumerator<Process>.Current
            {
                [DebuggerHidden]
                get
                {
                    return <>2__current;
                }
            }

            object IEnumerator.Current
            {
                [DebuggerHidden]
                get
                {
                    return <>2__current;
                }
            }

            [DebuggerHidden]
            public <Execute>d__0(int <>1__state)
            {
                this.<>1__state = <>1__state;
                <>l__initialThreadId = Environment.CurrentManagedThreadId;
            }

            [DebuggerHidden]
            void IDisposable.Dispose()
            {
                int num = <>1__state;
                if (num == -3 || num == 1)
                {
                    try
                    {
                    }
                    finally
                    {
                        <>m__Finally1();
                    }
                }
            }

            private bool MoveNext()
            {
                try
                {
                    int num = <>1__state;
                    if (num != 0)
                    {
                        if (num != 1)
                        {
                            return false;
                        }
                        <>1__state = -3;
                        goto IL_00bb;
                    }
                    <>1__state = -1;
                    <maxSizeBytes>5__1 = 52428800;
                    Debug.WriteLine("LimitByWorkingSetSize Enter");
                    <>s__2 = input.GetEnumerator();
                    <>1__state = -3;
                    goto IL_00c3;
                    IL_00bb:
                    <process>5__3 = null;
                    goto IL_00c3;
                    IL_00c3:
                    if (<>s__2.MoveNext())
                    {
                        <process>5__3 = <>s__2.Current;
                        Debug.WriteLine("LimitByWorkingSetSize foreach");
                        if (<process>5__3.WorkingSet64 > <maxSizeBytes>5__1)
                        {
                            Debug.WriteLine("LimitByWorkingSetSize yield");
                            <>2__current = <process>5__3;
                            <>1__state = 1;
                            return true;
                        }
                        goto IL_00bb;
                    }
                    <>m__Finally1();
                    <>s__2 = null;
                    return false;
                }
                catch
                {
                    //try-fault
                    ((IDisposable)this).Dispose();
                    throw;
                }
            }

            bool IEnumerator.MoveNext()
            {
                //ILSpy generated this explicit interface implementation from .override directive in MoveNext
                return this.MoveNext();
            }

            private void <>m__Finally1()
            {
                <>1__state = -1;
                if (<>s__2 != null)
                {
                    <>s__2.Dispose();
                }
            }

            [DebuggerHidden]
            void IEnumerator.Reset()
            {
                throw new NotSupportedException();
            }

            [DebuggerHidden]
            IEnumerator<Process> IEnumerable<Process>.GetEnumerator()
            {
                <Execute>d__0 <Execute>d__;
                if (<>1__state == -2 && <>l__initialThreadId == Environment.CurrentManagedThreadId)
                {
                    <>1__state = 0;
                    <Execute>d__ = this;
                }
                else
                {
                    <Execute>d__ = new <Execute>d__0(0);
                    <Execute>d__.<>4__this = <>4__this;
                }
                <Execute>d__.input = <>3__input;
                return <Execute>d__;
            }

            [DebuggerHidden]
            IEnumerator IEnumerable.GetEnumerator()
            {
                return ((IEnumerable<Process>)this).GetEnumerator();
            }
        }

        [IteratorStateMachine(typeof(<Execute>d__0))]
        public IEnumerable<Process> Execute(IEnumerable<Process> input)
        {
            <Execute>d__0 <Execute>d__ = new <Execute>d__0(-2);
            <Execute>d__.<>4__this = this;
            <Execute>d__.<>3__input = input;
            return <Execute>d__;
        }
    }
    internal class PrintProcessName : IOperation<Process>
    {
        [CompilerGenerated]
        private sealed class <Execute>d__0 : IEnumerable<Process>, IEnumerable, IEnumerator<Process>, IDisposable, IEnumerator
        {
            private int <>1__state;

            private Process <>2__current;

            private int <>l__initialThreadId;

            private IEnumerable<Process> input;

            public IEnumerable<Process> <>3__input;

            public PrintProcessName <>4__this;

            private IEnumerator<Process> <>s__1;

            private Process <process>5__2;

            Process IEnumerator<Process>.Current
            {
                [DebuggerHidden]
                get
                {
                    return <>2__current;
                }
            }

            object IEnumerator.Current
            {
                [DebuggerHidden]
                get
                {
                    return <>2__current;
                }
            }

            [DebuggerHidden]
            public <Execute>d__0(int <>1__state)
            {
                this.<>1__state = <>1__state;
                <>l__initialThreadId = Environment.CurrentManagedThreadId;
            }

            [DebuggerHidden]
            void IDisposable.Dispose()
            {
            }

            private bool MoveNext()
            {
                if (<>1__state != 0)
                {
                    return false;
                }
                <>1__state = -1;
                Debug.WriteLine("PrintProcessName Enter");
                <>s__1 = input.GetEnumerator();
                try
                {
                    while (<>s__1.MoveNext())
                    {
                        <process>5__2 = <>s__1.Current;
                        Debug.WriteLine("PrintProcessName  print");
                        Console.WriteLine(<process>5__2.ProcessName);
                        <process>5__2 = null;
                    }
                }
                finally
                {
                    if (<>s__1 != null)
                    {
                        <>s__1.Dispose();
                    }
                }
                <>s__1 = null;
                Debug.WriteLine("PrintProcessName break");
                return false;
            }

            bool IEnumerator.MoveNext()
            {
                //ILSpy generated this explicit interface implementation from .override directive in MoveNext
                return this.MoveNext();
            }

            [DebuggerHidden]
            void IEnumerator.Reset()
            {
                throw new NotSupportedException();
            }

            [DebuggerHidden]
            IEnumerator<Process> IEnumerable<Process>.GetEnumerator()
            {
                <Execute>d__0 <Execute>d__;
                if (<>1__state == -2 && <>l__initialThreadId == Environment.CurrentManagedThreadId)
                {
                    <>1__state = 0;
                    <Execute>d__ = this;
                }
                else
                {
                    <Execute>d__ = new <Execute>d__0(0);
                    <Execute>d__.<>4__this = <>4__this;
                }
                <Execute>d__.input = <>3__input;
                return <Execute>d__;
            }

            [DebuggerHidden]
            IEnumerator IEnumerable.GetEnumerator()
            {
                return ((IEnumerable<Process>)this).GetEnumerator();
            }
        }

        [IteratorStateMachine(typeof(<Execute>d__0))]
        public IEnumerable<Process> Execute(IEnumerable<Process> input)
        {
            <Execute>d__0 <Execute>d__ = new <Execute>d__0(-2);
            <Execute>d__.<>4__this = this;
            <Execute>d__.<>3__input = input;
            return <Execute>d__;
        }
    }
    internal class Pipeline<T>
    {
        private readonly List<IOperation<T>> operations = new List<IOperation<T>>();

        public Pipeline<T> Register(IOperation<T> operation)
        {
            operations.Add(operation);
            return this;
        }

        public void Execute()
        {
            IEnumerable<T> enumerable = null;
            List<IOperation<T>>.Enumerator enumerator = operations.GetEnumerator();
            try
            {
                while (enumerator.MoveNext())
                {
                    IOperation<T> current = enumerator.Current;
                    enumerable = current.Execute(enumerable);
                }
            }
            finally
            {
                ((IDisposable)enumerator).Dispose();
            }
            IEnumerator<T> enumerator2 = enumerable.GetEnumerator();
            while (enumerator2.MoveNext())
            {
                int num = 0;
            }
        }
    }
}

为了我的钱,我可能会抛开所有这些,只做:

Process.GetProcesses()
  .Where(p => p.BasePriority > 50*1024*1024)
  .Select(p => p.ProcessName)
  .ToList();

这里是关于 yield 的一点:

当方法 return 使用 yield 时,可以返回该方法并从我们停止的地方继续。看看这个不产生的方法:

int FibonacciSequence(){
  return 1;
  return 2;
  return 3;
  return 5;
}

当我们调用它时,我们只会得到 1。第一个 return 是一个硬停止;我们可以调用它一百万次,但只有第一个 return 会做任何事情。所有其他代码完全无法访问。

现在让我们改变它,让它产生:

IEnumerable<int> FibonacciSequence(){
  yield return 1;
  yield return 2;
  yield return 3;
  yield return 5;
}

它现在是可枚举的,所以现在我们将它称为将枚举它的东西的一部分:

foreach(var v in FibonacciSequence())
  Console.WriteLine(v);

编译器做了很多事情来将你的 foreach 转换成 while 获取一个枚举器并一遍又一遍地调用 movenext 但问题的关键是,当你枚举这个产生方法时它

  • returns 1 到 foreach 循环
  • 1 已打印
  • movenext 被 foreach 不可见地调用,这使得 c# 返回到“return 1”之后的方法,这次是 returns 2
  • 2 已打印
  • 回到yield return 2之后的方法,return 3
  • 3 已打印
  • 回来,return 5
  • 打印 5
  • back in, fall out of the end of the method, 停止枚举

所以当我们让步时,就好像它在方法中放置了一个标记,回到调用者,当我们回到方法时(通过移动迭代器),我们从从我们离开的地方开始标记,而不是从方法的第一行开始

您不必对收益进行硬编码;你可以写一个你可以永远枚举的方法:

IEnumerable<int> FibonacciSequence(){
  int prevprev = 0;
  int prev = 1;

  while(true){
    int toReturn = prevprev + prev;
    prevprev = prev;
    prev = toReturn;
    yield return toReturn;
  }
}

每次命中 yield 时,C# 都会返回到具有新值的枚举代码。因为这是一个循环,所以它会一直生成斐波那契直到它爆炸(如果你已经检查了数学,那么加法会导致溢出,或者你填满了你的记忆..或者如果你只是打印,它会永远走下去)。为了能够停止枚举,您需要从方法中 return 而不屈服(从方法末尾退出),或者执行 yield break