如何创建指向另一个 Action C# 的 Action?
how to make an Action that points to another Action C#?
我 运行 在 c# 中遇到了一些意外行为。我基本上是在尝试将一个动作分配给另一个动作的 reference,这样我以后就可以 subscribe/unsubcrib 方法对引用的动作 。我不想知道实现引用操作的 class。
问题是,应该指向我想听的动作的动作似乎并没有真正指向它。我认为只要引用的操作被引发,它就会引发,但显然事实并非如此。
我可能对代表有一些误解。有人可以告诉我我做错了什么吗?有没有解决我想要实现的目标的方法?
感谢任何回复!
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,它使此类事情形式化并使事件处理更易于管理。
简答
how to make an action that points to another action c#?
你不能。动作是委托,委托是不可变的。
the action that should be pointing to the action I want to listen to doesn't seem to actually point to it.
那是因为 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");
}
我 运行 在 c# 中遇到了一些意外行为。我基本上是在尝试将一个动作分配给另一个动作的 reference,这样我以后就可以 subscribe/unsubcrib 方法对引用的动作 。我不想知道实现引用操作的 class。
问题是,应该指向我想听的动作的动作似乎并没有真正指向它。我认为只要引用的操作被引发,它就会引发,但显然事实并非如此。
我可能对代表有一些误解。有人可以告诉我我做错了什么吗?有没有解决我想要实现的目标的方法?
感谢任何回复!
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,它使此类事情形式化并使事件处理更易于管理。
简答
how to make an action that points to another action c#?
你不能。动作是委托,委托是不可变的。
the action that should be pointing to the action I want to listen to doesn't seem to actually point to it.
那是因为 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");
}