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

时间:2018-06-08 01:02:40

标签: c# pointers delegates action ref

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

}

框架基础实施:

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 :(得分:2)

假设OnViewShown是一个事件,并且您希望在调用Play时开始侦听此事件,然后在事件触发时调用Finished,则以下内容将允许您执行此操作:

MainGrid = GetTemplateChild("MainGrid");
Binding MainGridWidthBinding = new Binding(nameof(PrValue)){
    Source = this,
    Mode = BindingMode.TwoWay,
    Converter = new DoubleModifierConverter 
    { 
        Modifier = 20.0, 
        Operator = Operator.Substraction 
    }
};
MainGrid.SetBinding(Grid.WidthProperty, MainGridWidthBinding);

使用以下方式调用:

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

请注意,您希望100%确定在播放后将触发OnViewShown,或者永远不会调用Finished,并且您还会有内存泄漏,因为您从未取消订阅该事件

虽然在Unity中可能无法使用它,但请查看System.Reactive,它会使这类事情正式化并使处理事件更易于管理。

答案 1 :(得分:2)

简答

  

如何制作一个指向另一个动作的动作c#?

你做不到。操作是委托,委托是不可变的。

  

应该指向我想听的动作的动作似乎并没有真正指向它。

这是因为delegates是不可变的。即使您传递了委托的ref,当您执行分配时,也会创建副本。代表就像strings那样。

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

实施例

以下是a Fiddle for you进一步说明。

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>Here it is as a Fiddle

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