使这项工作起作用的流畅对象模型是什么?

What is the fluent object model to make this work?

作为编写流畅 API 的练习,我想我会进行以下编译和 运行:

static void Main(string[] args)
{
    Enumerable.Range(1, 100)
        .When(i => i % 3 == 0).Then(i => Console.WriteLine("fizz"))
        .When(i => i % 5 == 0).Then(i => Console.WriteLine("buzz"))
        .Otherwise(i => Console.WriteLine(i))
        .Run();

    Console.ReadLine();
}

想法是 .When 将测试枚举中的每个元素,如果它通过谓词,则执行操作 运行。如果谓词失败,则该项目沿链向下传递。

我得出的图表是:

public static class EnumerableExtensions
{
    public static IConditionalEnumerable<T> When<T>(this IEnumerable<T> items, Predicate<T> test, Action<T> action)
    {
    }

    public static IResolvedEnumerable<T> Then<T>(this IConditionalEnumerable<T> items, Predicate<T> test, Action<T> action)
    {
    }

    public static void Run<T>(this IEnumerable<T> items)
    {
        foreach (var item in items) ;
    }
}

public interface IConditionalEnumerable<T> : IEnumerable<T>
{
    IResolvedEnumerable<T> Then<T>(IConditionalEnumerable<T> items, Action<T> action);
}

public interface IResolvedEnumerable<T> : IEnumerable<T>
{
    IEnumerable<T> Otherwise(Action<T> behavior);
}

我 运行 遇到了一个问题 -- When 不能 foreach / yield return 在里面,因为 return 类型不是直接 IEnumerable<T>(虽然它继承自它)。这让精神上的扳手陷入了困境。扩展方法的实现是什么样的?

这是我的尝试。我重命名了接口只是为了我的理解,因为我一起破解它。

public interface IWhen<T> {
    IThen<T> Then(Action<T> action);
}

public interface IThen<T> : IRun {
    IWhen<T> When(Func<T, bool> test);
    IRun Otherwise(Action<T> action);
}

public interface IRun {
    void Run();
}

public interface IRule<T> {
    Func<T, bool> Predicate { get; }
    Action<T> Invoke { get; }
}

并创建了以下实现。

public class Rule<T> : IRule<T> {
    public Rule(Func<T, bool> predicate, Action<T> action) {
        this.Predicate = predicate;
        this.Invoke = action;
    }
    public Func<T, bool> Predicate { get; private set; }
    public Action<T> Invoke { get; private set; }
}

public class Then<T> : IThen<T> {
    private Queue<IRule<T>> rules = new Queue<IRule<T>>();
    private IEnumerable<T> source;

    public Then(IEnumerable<T> source, Queue<IRule<T>> rules) {
        this.source = source;
        this.rules = rules;
    }

    public IWhen<T> When(Func<T, bool> test) {
        var temp = new When<T>(source, test, rules);
        return temp;
    }

    public void Run() {
        foreach (var item in source) {
            var rule = rules.FirstOrDefault(r => r.Predicate(item));
            if (rule == null) continue;
            rule.Invoke(item);
        }
    }

    public IRun Otherwise(Action<T> action) {
        var rule = new Rule<T>(s => true, action);
        rules.Enqueue(rule);
        return new Then<T>(source, rules);
    }
}

public class When<T> : IWhen<T> {
    private Queue<IRule<T>> rules;
    private Func<T, bool> test;
    private IEnumerable<T> source;

    public When(IEnumerable<T> source, Func<T, bool> test, Queue<IRule<T>> rules = null) {
        this.source = source;
        this.test = test;
        this.rules = rules ?? new Queue<IRule<T>>();
    }

    public IThen<T> Then(Action<T> action) {
        var rule = new Rule<T>(test, action);
        rules.Enqueue(rule);
        var then = new Then<T>(source, rules);
        return then;
    }
}

扩展方法看起来像这样开始。

public static class EnumerableExtensions {
    public static IWhen<T> When<T>(this IEnumerable<T> source, Func<T, bool> test) {
        var temp = new When<T>(source, test);
        return temp;
    }
}

下面的例子 .Net Fiddle

public class Program {
    public static void Main() {
         Enumerable.Range(1, 10)
              .When(i => i % 3 == 0).Then(i => Console.WriteLine("fizz"))
              .When(i => i % 5 == 0).Then(i => Console.WriteLine("buzz"))
              .Otherwise(i => Console.WriteLine(i))
              .Run();
    }
}

制作

1
2
fizz
4
buzz
fizz
7
8
fizz
buzz

不用担心序列。只是在价值观上做。

您完全可以使用扩展方法来做到这一点。事情是这样的:

  • 不知何故 Otherwise 需要知道是否有任何先前的 Then 被执行。
  • 因此,不知何故,每个 When 都需要知道之前的每个 Then 是否已执行。
  • 每个 Then 都需要知道前一个 When 是真还是假。

这是骨架:

static class X 
{
    public enum WhenState { DoIt, DoNot }
    public enum ThenState { DidIt, DidNot }
    public static (T, ThenState) Begin<T>(this T item) { ... }
    public static (T, WhenState, ThenState) When<T>(
      this (T item, ThenState then) tuple, 
      Func<T, bool> p) { ... }
    public static (T, ThenState) Then<T>(
      this (T item, WhenState when, ThenState then) tuple, 
      Action<T> a) { ... }
    public static void Otherwise<T>(
      this (T item, ThenState then) tuple, 
      Action<T> a) { ... }

一旦实现了这些扩展方法,您就可以:

3.Begin().When(x => x % 3 == 0).Then( ... )

等等。

一旦你实现了它,就很容易将操作提升到序列。我们有一个将价值观转化为行动的设备;什么是将一系列值转换为一系列动作的设备?它内置于语言中:foreach(var item in items) item.Begin()....

同样,很容易将操作提升到任何其他monad。说,可以为空。我们有一个将价值观转化为行动的设备。将可空值转换为动作或无动作的设备是什么?它内置于语言中:x?.Begin()...

假设您希望将操作应用于 Task<int>;将未来价值转化为未来行动的设备是什么? async Task DoIt(Task<T> task) { (await task).Begin()....

等等。 对值而不是提升值执行操作;使用语言的内置提升操作将您的操作应用于提升值以产生提升动作。