簡體   English   中英

將任何委托作為參數的構造方法

[英]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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM