从事件中收集值

时间:2017-05-18 17:53:54

标签: c# events functional-programming handler immutability

我有一个活动。 该事件不时被触发,它调用的事件处理程序为Action<int>

现在我想&#34;收集&#34;事件传递给我的这些整数并将它们保存在列表中。我还想指定列表完成并启动新列表的时刻。

我想到的天真解决方案是属性List<int> ValList。 事件处理程序每​​次调用时都会添加一个值。 消费者一方在需要的时候拿着名单,并且会不时地说ValList = new List<int>(); 为了避免线程同步问题,我也需要锁定。

我发现这个解决方案非常丑陋,并且想知道其他选择。 随着时间的推移,我越来越成为一名功能性程序员,而且我经常使用它。 但是当涉及到这样的问题时,我仍然在考虑程序问题。 我真的想避免使用可变列表(我仍在使用System.Collections.Immutable)。

是否有一个没有可变性和副作用的好功能解决方案?

2 个答案:

答案 0 :(得分:0)

您应该考虑使用Reactive Extensions。它处理值(事件)流,并且可以消除锁的需要。

首先,我要为AddCompleteRequestView操作定义一些操作类。这在F#中表现得像一个有区别的联合,例如:

public class EventAction
{
    public static EventAction Add(int value) => new AddAction(value);
    public static readonly RequestViewAction RequestView = new RequestViewAction();
    public static readonly EventAction Complete = new CompleteAction();
}

public class AddAction : EventAction
{
    public readonly int Value;
    public AddAction(int value) => Value = value;
}

public class CompleteAction : EventAction
{
}

public class RequestViewAction : EventAction
{
}

接下来,我要创建一个名为AggregateView的类型,它将包含三个Rx Subject值:

  • aggregator将收集EventAction个事件并管理汇总的Lst<int>Lst<int>是来自the language-ext functional language extensions library的不可变列表类型,但您可以使用{ {1}}也是。)
  • ImmutableList,它只是整数事件的流
  • events这将是views次观看
  • 的流

这是班级:

Lst<int>

它有两个using System; using LanguageExt; using static LanguageExt.Prelude; using System.Reactive.Linq; using System.Reactive.Subjects; public class AggregateView : IDisposable { readonly Subject<EventAction> aggregator = new Subject<EventAction>(); readonly Subject<int> events = new Subject<int>(); readonly Subject<Lst<int>> view = new Subject<Lst<int>>(); readonly IDisposable subscription; public AggregateView() { // Creates an aggregate view of the integers that responds to various control // actions coming through. subscription = aggregator.Aggregate( Lst<int>.Empty, (list, action) => { switch(action) { // Adds an item to the aggregate list and passes it on to the // events Subject case AddAction add: events.OnNext(add.Value); return list.Add(add.Value); // Clears the list and passes a list onto the views Subject case CompleteAction complete: view.OnNext(Lst<int>.Empty); return Lst<int>.Empty; // Gets the current aggregate list and passes it onto the // views Subject case RequestViewAction req: view.OnNext(list); return list; default: return list; } }) .Subscribe(x => { }); } /// <summary> /// Observable stream of integer events /// </summary> public IObservable<int> Events => events; /// <summary> /// Observable stream of list views /// </summary> public IObservable<Lst<int>> Views => view; /// <summary> /// Listener for plugging into an event /// </summary> public void Listener(int value) => aggregator.OnNext(EventAction.Add(value)); /// <summary> /// Clears the aggregate view and post it to Views /// </summary> public void Complete() => aggregator.OnNext(EventAction.Complete); /// <summary> /// Requests a the current aggregate view to be pushed through to /// the Views subscribers /// </summary> public void RequestView() => aggregator.OnNext(EventAction.RequestView); /// <summary> /// Dispose /// </summary> public void Dispose() { subscription?.Dispose(); view?.OnCompleted(); events?.OnCompleted(); view?.Dispose(); events?.Dispose(); } } 属性:

  • IObservable - 允许您订阅汇总列表
  • Views - 允许您订阅整数事件

还有一些有用的方法:

  • Events - 这就是您插入Listener
  • 的内容
  • event - 这将清空聚合列表并将空列表发送到Complete observable
  • View - 这会将当前的汇总列表发送给RequestView可观察的所有订阅者。

最后测试一下:

Views

这是我在处理事件时能够想到的最有效的方法。对于任何想要在功能上工作的人来说,class Program { static event Action<int> eventTest; static void Main(string[] args) { var aggregate = new AggregateView(); eventTest += aggregate.Listener; aggregate.Views.Subscribe(ReceiveList); aggregate.Events.Subscribe(ReceiveValue); eventTest(1); eventTest(2); eventTest(3); eventTest(4); eventTest(5); aggregate.RequestView(); aggregate.Complete(); eventTest(6); eventTest(7); eventTest(8); eventTest(9); eventTest(10); aggregate.RequestView(); } static void ReceiveList(Lst<int> list) => Console.WriteLine($"Got list of {list.Count} items: {ListShow(list)}"); static void ReceiveValue(int x) => Console.WriteLine(x); static string ListShow(Lst<int> list) => String.Join(", ", list); } 应始终是红旗,因为默认情况下它具有副作用并且不纯净。因此,您需要尽可能地封装副作用并使其他一切变得纯净。

顺便说一句,你可以将这整个事物概括为适用于任何类型。这使它更有用:

Action<int>

答案 1 :(得分:-1)

如果我完全理解您所说的事件已被锁定为操作,并且您无法控制该事件签名。您希望收集传递的每个int,直到某个外部请求来检索具有任何累积整数的列表,此时列表将重置并保存有关该集合的时间信息。这是对的吗?

让我感到困惑的是你为什么用一种意味着OOP的语言提到函数式编程?你可能会认为LINQ在某种程度上是功能性的,但是对于具有功能意识的人来说肯定有更好的选择吗?因为这对于累加器管理器类来说似乎是一个非常简单的解决方案。

namespace bs
{
struct CollectionEvent
{
    public DateTime Retrieved { get; set; }      
    public String IP { get; set; }  
}

static class Accumulator
{
    private static List<int> Items { get; set; } = new List<int>();
    private static bool Mutex { get; set; } = false;
    private static List<CollectionEvent> Collections { get; set; } = new List<CollectionEvent>();

    public static void Add(int i)
    {
        Sync(() => Items.Add(i));
    }

    public static List<int> Retrieve(String IP)
    {
        Collections.Add(new CollectionEvent
        {
            Retrieved = DateTime.UtcNow,
            IP = IP
        });

        List<int> dataOut = null;
        Sync(() =>
        {
            dataOut = new List<int>(Items);
            Items = new List<int>();
        });

        return dataOut;
    }

    public static void Sync(Action fn)
    {
        const int Threshold = 10;
        int attempts = 0;

        for (; Mutex && (attempts < Threshold); attempts++)
            Thread.Sleep(100 * attempts);

        if (attempts == Threshold)
            throw new Exception(); // or something better I'm sure

        Mutex = true;
        fn();
        Mutex = false;
    }
}

class Program
{
    static void Main(string[] args)
    {
        var r = new Random();
        var m = r.Next(5, 10);

        for (int i = 0; i < m; i++)
        {
            var datum = r.Next(100, 10000);
            Console.WriteLine($"COLLECT {datum}");
            Accumulator.Add(datum);
        }

        Console.WriteLine("RETRIEVE");
        Accumulator.Retrieve("0.0.0.0").ForEach(i => Console.WriteLine($"\t{i}"));

        m = r.Next(5, 10);
        for (int i = 0; i < m; i++)
        {
            var datum = r.Next(100, 10000); 
            Console.WriteLine($"COLLECT {datum}");
            Accumulator.Add(datum);
        }

        Console.WriteLine("RETRIEVE");
        Accumulator.Retrieve("0.0.0.0").ForEach(i => Console.WriteLine($"\t{i}"));

        Console.Read();
    }
}
}