C# 订阅一个函数到 System.Action 订阅另一个 System.Action:为什么顺序很重要?
C# subscribe a function to System.Action that is subscribed to another System.Action: why does order matter?
我有3个类,组成一棵树。我正在使用 System.Actions 来传达事件。但我很困惑为什么我订阅的顺序很重要。检查这个伪代码:
class Leaf
{
public System.Action OnTrigger;
public void Go()
{
if (OnTrigger != null)
OnTrigger();
}
}
class Chunk
{
public System.Action OnTrigger;
public Leaf leaf = null;
public Chunk()
{
leaf = new Leaf();
}
}
class Tree
{
void Hello()
{
Debug.Log("Hello");
}
void World()
{
Debug.Log("World");
}
public Tree()
{
var chunk2 = new Chunk();
chunk2.OnTrigger += Hello; // OK: will be called
chunk2.leaf.OnTrigger += chunk2.OnTrigger;
chunk2.OnTrigger += World; // NOT: not be called
chunk2.leaf.Go();
}
}
此致,
马萨
这与订单本身无关。
它与您所做的作业有关。
chunk2.OnTrigger += Hello; // this is **never** called
chunk2.leaf.OnTrigger += chunk2.OnTrigger; // this is the only one called
chunk2.OnTrigger += World; // this is **never** called
当您调用 chunk2.leaf.Go();
时,唯一触发的事件是 chunk2.leaf.OnTrigger
。 chunk2.OnTrigger
从未被调用。
通过检查委托的工作方式和编译时创建的 MSIL,这一点相对明显。首先让我们注意 System.Action
是一个具有 ICloneable
接口的 System.Delegate
。
当你编译这个程序时,你得到的几乎是这样的:
IL_0015: newobj instance void [mscorlib]System.Action::.ctor(object,
native int)
IL_001a: call class [mscorlib]System.Delegate [mscorlib]System.Delegate::Combine(class [mscorlib]System.Delegate,
class [mscorlib]System.Delegate)
IL_001f: castclass [mscorlib]System.Action
IL_0024: stfld class [mscorlib]System.Action ConsoleTests.Program6/Chunk::OnTrigger
IL_0029: ldloc.0
IL_002a: ldfld class ConsoleTests.Program6/Leaf ConsoleTests.Program6/Chunk::leaf
IL_002f: dup
IL_0030: ldfld class [mscorlib]System.Action ConsoleTests.Program6/Leaf::OnTrigger
IL_0035: ldloc.0
IL_0036: ldfld class [mscorlib]System.Action ConsoleTests.Program6/Chunk::OnTrigger
IL_003b: call class [mscorlib]System.Delegate [mscorlib]System.Delegate::Combine(class [mscorlib]System.Delegate,
class [mscorlib]System.Delegate)
IL_0040: castclass [mscorlib]System.Action
IL_0045: stfld class [mscorlib]System.Action ConsoleTests.Program6/Leaf::OnTrigger
IL_004a: ldloc.0
IL_004b: dup
IL_004c: ldfld class [mscorlib]System.Action ConsoleTests.Program6/Chunk::OnTrigger
IL_0051: ldnull
IL_0052: ldftn void ConsoleTests.Program6::World()
IL_0058: newobj instance void [mscorlib]System.Action::.ctor(object,
native int)
IL_005d: call class [mscorlib]System.Delegate [mscorlib]System.Delegate::Combine(class [mscorlib]System.Delegate,
class [mscorlib]System.Delegate)
IL_0062: castclass [mscorlib]System.Action
IL_0067: stfld class [mscorlib]System.Action ConsoleTests.Program6/Chunk::OnTrigger
IL_006c: ldloc.0
IL_006d: ldfld class ConsoleTests.Program6/Leaf ConsoleTests.Program6/Chunk::leaf
IL_0072: callvirt instance void ConsoleTests.Program6/Leaf::Go()
请注意,当您对委托执行 +=
时,编译器会将其转换为 Combine
调用,该调用将当前委托与传入的委托组合在一起。现在看IL_004b,这是一个dup
调用。
From the MSDN: "Copies the current topmost value on the evaluation stack, and then pushes the copy onto the evaluation stack."
这会告诉运行时复制堆栈上的对象,因为这是 ICloneable
,它会创建对象的 copy。现在,当您将另一个委托添加到它时,您并没有将它添加到两者,只是您正在操作的那个,让 Leaf
触发器保持原样。
几乎等价的代码是这样的:
public Tree()
{
var chunk2 = new Chunk();
chunk2.OnTrigger += Hello; // OK: will be called
chunk2.leaf.OnTrigger = (Action)chunk2.OnTrigger.Clone();
chunk2.OnTrigger += World; // NOT: not be called
chunk2.leaf.Go();
}
这可能会使这里发生的事情更清楚一些。
我有3个类,组成一棵树。我正在使用 System.Actions 来传达事件。但我很困惑为什么我订阅的顺序很重要。检查这个伪代码:
class Leaf
{
public System.Action OnTrigger;
public void Go()
{
if (OnTrigger != null)
OnTrigger();
}
}
class Chunk
{
public System.Action OnTrigger;
public Leaf leaf = null;
public Chunk()
{
leaf = new Leaf();
}
}
class Tree
{
void Hello()
{
Debug.Log("Hello");
}
void World()
{
Debug.Log("World");
}
public Tree()
{
var chunk2 = new Chunk();
chunk2.OnTrigger += Hello; // OK: will be called
chunk2.leaf.OnTrigger += chunk2.OnTrigger;
chunk2.OnTrigger += World; // NOT: not be called
chunk2.leaf.Go();
}
}
此致,
马萨
这与订单本身无关。
它与您所做的作业有关。
chunk2.OnTrigger += Hello; // this is **never** called
chunk2.leaf.OnTrigger += chunk2.OnTrigger; // this is the only one called
chunk2.OnTrigger += World; // this is **never** called
当您调用 chunk2.leaf.Go();
时,唯一触发的事件是 chunk2.leaf.OnTrigger
。 chunk2.OnTrigger
从未被调用。
通过检查委托的工作方式和编译时创建的 MSIL,这一点相对明显。首先让我们注意 System.Action
是一个具有 ICloneable
接口的 System.Delegate
。
当你编译这个程序时,你得到的几乎是这样的:
IL_0015: newobj instance void [mscorlib]System.Action::.ctor(object, native int) IL_001a: call class [mscorlib]System.Delegate [mscorlib]System.Delegate::Combine(class [mscorlib]System.Delegate, class [mscorlib]System.Delegate) IL_001f: castclass [mscorlib]System.Action IL_0024: stfld class [mscorlib]System.Action ConsoleTests.Program6/Chunk::OnTrigger IL_0029: ldloc.0 IL_002a: ldfld class ConsoleTests.Program6/Leaf ConsoleTests.Program6/Chunk::leaf IL_002f: dup IL_0030: ldfld class [mscorlib]System.Action ConsoleTests.Program6/Leaf::OnTrigger IL_0035: ldloc.0 IL_0036: ldfld class [mscorlib]System.Action ConsoleTests.Program6/Chunk::OnTrigger IL_003b: call class [mscorlib]System.Delegate [mscorlib]System.Delegate::Combine(class [mscorlib]System.Delegate, class [mscorlib]System.Delegate) IL_0040: castclass [mscorlib]System.Action IL_0045: stfld class [mscorlib]System.Action ConsoleTests.Program6/Leaf::OnTrigger IL_004a: ldloc.0 IL_004b: dup IL_004c: ldfld class [mscorlib]System.Action ConsoleTests.Program6/Chunk::OnTrigger IL_0051: ldnull IL_0052: ldftn void ConsoleTests.Program6::World() IL_0058: newobj instance void [mscorlib]System.Action::.ctor(object, native int) IL_005d: call class [mscorlib]System.Delegate [mscorlib]System.Delegate::Combine(class [mscorlib]System.Delegate, class [mscorlib]System.Delegate) IL_0062: castclass [mscorlib]System.Action IL_0067: stfld class [mscorlib]System.Action ConsoleTests.Program6/Chunk::OnTrigger IL_006c: ldloc.0 IL_006d: ldfld class ConsoleTests.Program6/Leaf ConsoleTests.Program6/Chunk::leaf IL_0072: callvirt instance void ConsoleTests.Program6/Leaf::Go()
请注意,当您对委托执行 +=
时,编译器会将其转换为 Combine
调用,该调用将当前委托与传入的委托组合在一起。现在看IL_004b,这是一个dup
调用。
From the MSDN: "Copies the current topmost value on the evaluation stack, and then pushes the copy onto the evaluation stack."
这会告诉运行时复制堆栈上的对象,因为这是 ICloneable
,它会创建对象的 copy。现在,当您将另一个委托添加到它时,您并没有将它添加到两者,只是您正在操作的那个,让 Leaf
触发器保持原样。
几乎等价的代码是这样的:
public Tree()
{
var chunk2 = new Chunk();
chunk2.OnTrigger += Hello; // OK: will be called
chunk2.leaf.OnTrigger = (Action)chunk2.OnTrigger.Clone();
chunk2.OnTrigger += World; // NOT: not be called
chunk2.leaf.Go();
}
这可能会使这里发生的事情更清楚一些。