跟踪操作<T> lambda 作为操作<object>
Track Action<T> lambdas as Action<object>
过去问过的类似问题:
- Convert Action<T> to Action<object>
- Expose Action<T> as Action<object>
- How to cast object to Action<T>
全部解决了将 Action<T> myActionT
包装成匿名 new Action<object>(o => myActionT((T)o));
我的用例是初始的类型化 lambda 是一个数据订阅回调,将其包装成一个匿名回调意味着我失去了使用原始 Action<T>
取消订阅的能力
我将范围缩小到这个样板示例:
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
(因为我实际上只需要值类型)
是否有 better/more 优雅的方式来正确跟踪通用 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 选择)
超级简单。试试这个:
private List<Delegate> callbacks = new List<Delegate>();
C# 编译器允许您将任何 Action<T>
转换为 Delegate
。您的代码可以通过该更改正常编译和运行。
但是,我建议将您的 Subscribe
方法签名更改为:
IDisposable Subscribe<T>(Action<T> cbk)
那么您就不需要 Unsubscribe
方法 - 这需要您保留对 Action<T> 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();
}
}
}
过去问过的类似问题:
- Convert Action<T> to Action<object>
- Expose Action<T> as Action<object>
- How to cast object to Action<T>
全部解决了将 Action<T> myActionT
包装成匿名 new Action<object>(o => myActionT((T)o));
我的用例是初始的类型化 lambda 是一个数据订阅回调,将其包装成一个匿名回调意味着我失去了使用原始 Action<T>
我将范围缩小到这个样板示例:
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
(因为我实际上只需要值类型)
是否有 better/more 优雅的方式来正确跟踪通用 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 选择)
超级简单。试试这个:
private List<Delegate> callbacks = new List<Delegate>();
C# 编译器允许您将任何 Action<T>
转换为 Delegate
。您的代码可以通过该更改正常编译和运行。
但是,我建议将您的 Subscribe
方法签名更改为:
IDisposable Subscribe<T>(Action<T> cbk)
那么您就不需要 Unsubscribe
方法 - 这需要您保留对 Action<T> 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();
}
}
}