[英]Constructor that takes any delegate as a parameter
這是簡化的案例。 我有一個類來存儲它將在完成時調用的委托:
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);
}
}
我擔心你不能做你所要求的。
首先,你不能被代表約束。 與合法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 += () => { Console.WriteLine("trigger"); };
只是將處理程序附加到trigger
而不是bar
。
您可以完成所有這些工作的方法是停止使用事件並使用Microsoft的Reactive Extensions(NuGet“Rx-Main”),它允許您將事件轉換為可以IObservable<T>
的基於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()
來.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();
那仍然是正確的輸出。
在.NET中,已經存在一個不接收任何參數的委托,它是Action
所以你的動畫類可能是這樣的:
public class Animation
{
public event Action OnEnd;
}
但是您可以將事件作為參數傳遞,如果您嘗試將收到此編譯錯誤
該事件只能出現在+ =或 - =“的左側
所以我們創建一個接口,並在那里聲明事件
public interface IAnimation
{
event Action OnEnd;
}
使用接口方法你沒有外部依賴,你可以有許多實現它的類,也是一個很好的實踐,取決於抽象而不是具體類型。 有一個名為SOLID的首字母縮略詞,它解釋了關於更好的OO代碼的5條原則。
然后你的動畫類實現了它
實驗值:該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
如果您正在使用多線程,則必須小心謹慎以避免出現Null Reference Exception
讓我們再看一下我為測試添加的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,但是對於多線程解決方案,您可能不會。 這是因為在執行if (OnEnd != null)
和OnEnd();
之間可以將OnEnd事件設置為null OnEnd();
Jon Skeet有一篇很好的文章,你可以看到C#6的Clean事件處理程序調用
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.