簡體   English   中英

如何制作一個指向另一個Action C#的Action?

[英]how to make an Action that points to another Action C#?

我在C#中遇到了一些意外行為。 我基本上是想將一個動作分配給另一個動作的引用 ,以便以后可以對所引用的動作進行訂閱/取消訂閱方法。 我不想知道實現引用動作的類。

問題是,應該指向我想聽的動作的動作似乎並沒有真正指向它。 我以為只要提到引用的動作,它就會增加,但是顯然不是這樣。

我可能對代表有些誤解。 有人可以告訴我我在做什么錯嗎? 我要達到的目標是否有解決方案?

感謝您的任何回應!

public class WaitForActionProcess : IProcess
{
    public Action Finished { get; set; }
    Action actionHandler;

    public WaitForActionProcess(ref Action action)
    {
        actionHandler = action;
    }

    public void Play()
    {
        actionHandler += RaiseFinished;
    }

    public void RaiseFinished()
    {
        actionHandler -= RaiseFinished;

        if(Finished != null)
        {
            Finished();
        }
    }
}

使用示例:

public class ReturnToMainMenuFrame : TutorialEventFrame
{
    [SerializeField]
    TutorialDialogueData dialogueData;

    [SerializeField]
    PointingArrowData arrowData;

    [SerializeField]
    TutorialDialogue tutorialDialogue;

    [SerializeField]
    PointingArrow arrow;

    [SerializeField]
    MainView mainView;

    public override void StartFrame()
    {
        frameProcesses.Add(new ShowPointToUIProcess(arrow, arrowData));
        frameProcesses.Add(new ShowDialogueProcess(tutorialDialogue, dialogueData));
        frameProcesses.Add(new WaitForActionProcess(ref mainView.OnViewShown));
        frameProcesses.Add(new HideDialogueProcess(tutorialDialogue, this));
        frameProcesses.Add(new HidePointToUIProcess(arrow,this));
        base.StartFrame();
    }

}

框架基礎實現:

public class TutorialEventFrame : MonoBehaviour {

    public delegate void OnFrameEnded();
    public event OnFrameEnded FrameEnded;

    public List<IProcess> frameProcesses = new List<IProcess>();

    public bool debugMode = false;

    public virtual void StartFrame()
    {
        StartProcess(0);
    }

    void StartProcess(int processIndex)
    {
        if (processIndex < frameProcesses.Count)
        {
            int nextProcessIndex = processIndex + 1;
            frameProcesses[processIndex].Finished += () => StartProcess(nextProcessIndex);
        }
        else
        {
            EndFrame();
            return;
        }

        if (debugMode)
        {
            Debug.Log("Starting process: " + frameProcesses[processIndex] + " of processes: " + (processIndex + 1) + "/" + (frameProcesses.Count - 1) + " on frame: " + name);
        }

        frameProcesses[processIndex].Play();           
    }

    public virtual void EndFrame() {

        foreach (var process in frameProcesses)
        {
            process.Finished = null;
        }

        if (debugMode)
        {
            Debug.Log("frame: " + name + " finished");
        }
        frameProcesses.Clear();

        if (FrameEnded != null) {
            FrameEnded();
        }
    }
}

假設OnViewShown是一個事件,並且您想在調用Play時開始偵聽此事件,然后在事件觸發時調用Finished,則可以執行以下操作:

public class WaitForActionProcess : IProcess
{
    public Action Finished { get; set; }

    Action<Action> Subscribe { get; }
    Action<Action> Unsubscribe { get; }

    public WaitForActionProcess(Action<Action> subscribe, Action<Action> unsubscribe)
    {
        Subscribe = subscribe;
        Unsubscribe = unsubscribe;
    }

    public void Play()
    {
        Subscribe(RaiseFinished);
    }

    public void RaiseFinished()
    {
        Unsubscribe(RaiseFinished);
        Finished?.Invoke();
    }
}

稱為:

 var wfap = new WaitForActionProcess(
       h => mainView.OnViewShown += h, 
       h => mainView.OnViewShown -= h);

請注意,您要100%確保在播放后觸發OnViewShown,否則將永遠不會調用Finished,並且您也將發生內存泄漏,因為您永遠不會取消訂閱該事件。

盡管在Unity中可能無法使用它,但是請查找System.Reactive,它使這種事情形式化,並使事件處理變得更易於管理。

簡短答案

如何做出指向另一個動作C#的動作?

你不能。 動作是代表,代表是不可變的。

應該指向我想聽的動作的動作似乎並沒有真正指向它。

那是因為delegates是不可變的。 即使您傳遞了委托的ref ,您在執行分配時也會創建一個副本。 代表就像strings一樣。

public WaitForActionProcess(ref Action action)
{
    // assignment creates a copy of a delegate
    actionHandler = action;
}

這是為您准備的小提琴,進一步進行了演示。

public class Program
{
    static Action action1;
    static Action actionHandler;
    public static void Main()
    {
        WaitForActionProcess(ref action1);
    }

    public static void WaitForActionProcess(ref Action action)
    {
        // this is still a reference to action1
        action += Both;

        // assignment creates a copy of a delegate
        actionHandler = action;

        // action is still a reference to action1
        // but actionHandler is a *copy* of action1
        action += OnlyAction1;
        actionHandler += OnlyActionHandler;

        action();
        // Both
        // OnlyAction1

        actionHandler();
        // Both
        // OnlyAction2
    }

    public static void Both()=> Console.WriteLine("Both");
    public static void OnlyAction1() => Console.WriteLine("OnlyAction1");
    public static void OnlyActionHandler() => Console.WriteLine("OnlyActionHandler");
}

可能的解決方法

請改用List<Action> 這里是小提琴

using System;
using System.Collections.Generic;

public class Program
{
    static List<Action> action1 = new List<Action>();
    static List<Action> actionHandler;
    public static void Main()
    {
        WaitForActionProcess(action1);
    }

    public static void WaitForActionProcess(List<Action> action)
    {
        action.Add(Both);

        // assignment passes a reference to the List
        actionHandler = action;

        action.Add(OnlyAction1);
        actionHandler.Add(OnlyActionHandler);

        // now things work nicely
        foreach(var a in action) a();
        foreach(var a in actionHandler) a();
    }

    public static void Both()=> Console.WriteLine("Both");
    public static void OnlyAction1() => Console.WriteLine("OnlyAction1");
    public static void OnlyActionHandler() => Console.WriteLine("OnlyActionHandler");
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

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