【问题标题】:Track Action<T> lambdas as Action<object>将 Action<T> lambda 跟踪为 Action<object>
【发布时间】:2020-07-22 09:46:55
【问题描述】:

过去提出的类似问题:

所有人都找到了将Action&lt;T&gt; myActionT 包装成匿名new Action&lt;object&gt;(o =&gt; myActionT((T)o)); 的解决方案
我的用例是初始的类型化 lambda 是一个数据订阅回调,将其包装成一个匿名回调意味着我无法使用原始 Action&lt;T&gt; 取消订阅。

我将其缩小到这个样板示例:

    internal class Demo
    {
        public class Dispatcher
        {
            private List<Action<object>> callbacks = new List<Action<object>>();

            public void Subscribe<T>(Action<T> cbk)
            {
                callbacks.Add(cbk); // <- compile error, Action<T> is contravariant
            }
            
            public void Unsubscribe<T>(Action<T> cbck)
            {
                callbacks.Remove(cbck); // <- compile error
            }
        }

        private static void Handler(int v) {}
        private static void Handler(string v) {}

        public static void Main()
        {
            var dispatcher = new Dispatcher();
            dispatcher.Subscribe<int>(Handler);
            dispatcher.Subscribe<string>(Handler);

            dispatcher.Unsubscribe<int>(Handler);
        }
    }

当然我可以重载Subscribe(因为我实际上只需要值类型)
是否有更好/更优雅的方法来正确跟踪通用 Action 回调?

稍后编辑:添加有关我的用例的更多详细信息:

  • 我包装了键值(字符串)更新的数据馈送
  • 客户端可以订阅字段,并且可以选择接收从字符串转换为其他类型的值 (我用TypeDescriptor.GetConverter(type).ConvertFromString(fieldValue)来处理转化)

例如:

 Subscribe<decimal>("price", (decimal px) => { /* do smthg with price*/ })
 Subscribe<bool>("opened", (bool opened) => { ... })
 Subscribe<string>("code", (string code) => { ... })

我觉得我目前的方法(函数指针/委托,跟踪订阅类型)陷入了困境 但我没有找到更直接的方法来实现这一点(除了用我需要的类型重载Subscribe,这可能是明智的 KISS 选择)

【问题讨论】:

  • “因为我实际上只需要值类型”。 string 不是值类型。
  • 除了订阅/取消订阅之外,您还打算如何处理callbacks。你也看过events吗?
  • 我添加了一些关于我如何使用callbacks的更多细节@
  • @AlinMotogna - 您是否考虑过为此使用 Microsoft 的 Rx?
  • 直到现在才听说过,对于我的简单用例来说看起来很重,但我会明确地研究它。谢谢!

标签: c# generics delegates


【解决方案1】:

超级简单。试试这个:

private List<Delegate> callbacks = new List<Delegate>();

C# 编译器允许您将任何Action&lt;T&gt; 转换为Delegate。您的代码可以通过该更改编译并运行良好。


但是,我建议将您的 Subscribe 方法签名更改为:

IDisposable Subscribe<T>(Action<T> cbk)

那么您就不需要Unsubscribe 方法——这需要您保留对Action&lt;T&gt; cbk 的引用。相反,您只需在订阅时调用 .Dispose()

试试这个代码:

internal class Demo
{
    public class Dispatcher
    {
        private List<Delegate> callbacks = new List<Delegate>();

        public IDisposable Subscribe<T>(Action<T> cbk)
        {
            callbacks.Add(cbk);
            return new AnonymousDisposable(() => callbacks.Remove(cbk));
        }
    }

    private static void Handler(int v) { }
    private static void Handler(string v) { }

    public static void Main()
    {
        var dispatcher = new Dispatcher();
        var subscription = dispatcher.Subscribe<int>(Handler);
        dispatcher.Subscribe<string>(Handler);

        subscription.Dispose();
    }
}

public sealed class AnonymousDisposable : IDisposable
{
    private readonly Action _action;
    private int _disposed;

    public AnonymousDisposable(Action action)
    {
        _action = action;
    }

    public void Dispose()
    {
        if (Interlocked.Exchange(ref _disposed, 1) == 0)
        {
            _action();
        }
    }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-09-09
    • 2018-04-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多