这是简化的案例。我有一个类来存储它将在完成时调用的委托:
public class Animation
{
public delegate void AnimationEnd();
public event AnimationEnd OnEnd;
}
我有另一个实用工具类,我想订阅各种代表。在构造我想要自己注册代表,但除此之外它不关心类型。问题是,我不知道如何在类型系统中表达。这是我的伪 - C#
public class WaitForDelegate
{
public delegateFired = false;
// How to express the generic type here?
public WaitForDelegate<F that's a delegate>(F trigger)
{
trigger += () => { delegateFired = true; };
}
}
提前致谢!
感谢Alberto Monteiro,我只使用System.Action
作为活动的类型。我现在的问题是,如何将事件传递给构造函数以便它可以自己注册?这可能是一个非常愚蠢的问题。
public class Example
{
Animation animation; // assume initialized
public void example()
{
// Here I can't pass the delegate, and get an error like
// "The event can only appear on the left hand side of += or -="
WaitForDelegate waiter = new WaitForDelegate(animation.OnEnd);
}
}
答案 0 :(得分:4)
我担心你不能做你所要求的。
首先,你不能被代表约束。与合法C#最接近的代码是:
public class WaitForDelegate<F> where F : System.Delegate
{
public bool delegateFired = false;
public WaitForDelegate(F trigger)
{
trigger += () => { delegateFired = true; };
}
}
但它不会编译。
但更大的问题是,无论如何你都无法像这样传递代表。
考虑这个简化的类:
public class WaitForDelegate
{
public WaitForDelegate(Action trigger)
{
trigger += () => { Console.WriteLine("trigger"); };
}
}
然后我尝试使用它:
Action bar = () => Console.WriteLine("bar");
var wfd = new WaitForDelegate(bar);
bar();
唯一的输出是:
bar
单词trigger
未出现。这是因为委托是按值复制的,因此行trigger += () => { Console.WriteLine("trigger"); };
仅将处理程序附加到trigger
而不是bar
。
您可以完成所有这些工作的方法是停止使用事件并使用Microsoft的Reactive Extensions(NuGet“Rx-Main”),它允许您将事件转换为可以获取的基于LINQ的IObservable<T>
实例传了过来。
以上是我上面的示例代码的工作原理:
public class WaitForDelegate
{
public WaitForDelegate(IObservable<Unit> trigger)
{
trigger.Subscribe(_ => { Console.WriteLine("trigger"); });
}
}
你现在称之为:
Action bar = () => Console.WriteLine("bar");
var wfd = new WaitForDelegate(Observable.FromEvent(h => bar += h, h => bar -= h));
bar();
现在产生输出:
bar
trigger
请注意,Observable.FromEvent
调用包含在有权访问的作用域中附加和分离处理程序的代码。它允许通过调用.Dispose()
来解除最终订阅呼叫的连接。
我让这个课很简单,但更完整的版本就是这个:
public class WaitForDelegate : IDisposable
{
private IDisposable _subscription;
public WaitForDelegate(IObservable<Unit> trigger)
{
_subscription = trigger.Subscribe(_ => { Console.WriteLine("trigger"); });
}
public void Dispose()
{
_subscription.Dispose();
}
}
如果您不想充分利用Rx,另一种方法是:
public class WaitForDelegate : IDisposable
{
private Action _detach;
public WaitForDelegate(Action<Action> add, Action<Action> remove)
{
Action handler = () => Console.WriteLine("trigger");
_detach = () => remove(handler);
add(handler);
}
public void Dispose()
{
if (_detach != null)
{
_detach();
_detach = null;
}
}
}
你这样称呼它:
Action bar = () => Console.WriteLine("bar");
var wfd = new WaitForDelegate(h => bar += h, h => bar -= h);
bar();
那仍然是正确的输出。
答案 1 :(得分:2)
在.NET中,已经有一个代理人没有收到任何参数,它是Action
所以你的动画类可能就是这样:
public class Animation
{
public event Action OnEnd;
}
但是您可以将事件作为参数传递,如果您尝试将收到此编译错误
该事件只能出现在+ =或 - =&#34;
的左侧
所以我们创建一个接口,并在那里声明事件
public interface IAnimation
{
event Action OnEnd;
}
使用接口方法,您没有外部依赖关系,并且您可以拥有许多实现它的类,这也是一种很好的实践,取决于抽象而不是具体类型。有一个名为SOLID的缩写,解释了关于更好的OO代码的5条原则。
然后你的动画类实现了
Obs。: CallEnd 方法仅用于测试目的
public class Animation : IAnimation
{
public event Action OnEnd;
public void CallEnd()
{
OnEnd();
}
}
现在你WaitForDelegate会收到一个 IAnimation ,所以这个类可以处理任何实现 IAnimation 类的类
public class WaitForDelegate<T> where T : IAnimation
{
public WaitForDelegate(T animation)
{
animation.OnEnd += () => { Console.WriteLine("trigger"); };
}
}
然后我们可以使用以下代码测试我们所做的代码
public static void Main(string[] args)
{
var a = new Animation();
var waitForDelegate = new WaitForDelegate<IAnimation>(a);
a.CallEnd();
}
结果是
触发
这是dotnetfiddle上的工作版本
https://dotnetfiddle.net/1mejBL
如果您正在使用多线程,则必须小心谨慎以避免空引用异常
让我们再次查看我为测试添加的CallEnd方法
public void CallEnd()
{
OnEnd();
}
OnEnd事件可能没有值,然后如果你试图调用它,你将收到Null Reference Exception。
因此,如果您使用的是C#5或更低版本,请执行以下操作
public void CallEnd()
{
var @event = OnEnd;
if (@event != null)
@event();
}
使用C#6可能就像那样
public void CallEnd()
=> OnEnd?.Invoke();
更多解释,您可以拥有此代码
public void CallEnd()
{
if (OnEnd != null)
OnEnd();
}
上面的代码可能会让您认为您可以安全地使用Null Reference Exception,但是对于多线程解决方案,您不是。这是因为OnEnd事件可以在执行if (OnEnd != null)
和OnEnd();
之间设置为空
Jon Skeet有一篇很好的文章,你可以看到Clean event handler invocation with C# 6