Как сделать действие, которое указывает на другое действие C #? - PullRequest
0 голосов
/ 08 июня 2018

Я столкнулся с неожиданным поведением в c #.Я в основном пытаюсь назначить действие для ref элемента другого действия, чтобы впоследствии я мог подписать / отписать методы для указанного действия .Я не хочу знать класс, который реализует упомянутое действие.

Проблема в том, что действие, которое должно указывать на действие, которое я хочу прослушать, похоже, на самом деле не указывает на него,Я думал, что он будет подниматься всякий раз, когда упоминается упомянутое действие, но, видимо, это не так.Может кто-нибудь сказать мне, что я делаю не так?Есть ли решение того, чего я пытаюсь достичь?

Я ценю любые ответы!

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();
    }

}

Реализация Frame Base:

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();
        }
    }
}

Ответы [ 2 ]

0 голосов
/ 08 июня 2018

Краткий ответ

как сделать действие, которое указывает на другое действие 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");
}
0 голосов
/ 08 июня 2018

Если предположить, что 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 будет запущен после Play, или Finished никогда не будет вызываться, и у вас также будет утечка памяти, как вы 'Вы никогда не отмените подписку на событие.

Хотя оно может быть недоступно для вас в Unity, найдите System.Reactive, который формализует подобные вещи и делает работу с событиями намного более управляемой.

...