【C#】Delegate()和Delegate.Invoke()

英文原文:

https://www.jacksondunstan.com/articles/3283

 在阅读其他人的C#代码时,我总是看到一些程序员像调用函数-del()那样调用委托,而另一些程序员则使用Delegate类的Invoke方法:del.Invoke()。这两者有什么不同吗?其中一个比另一个好吗?
 译者:先说结论两者一样。

 以下是今天的两位候选人:

// Create a delegate (using System.Action and a no-op lambda)
Action del = () => {};
 
// Call with function style
del();
 
// Call with Invoke
del.Invoke();

 “函数样式”版本使用重载()运算符,而调用版本使用Delegate.Invoke方法。这两个函数都将参数(如果有的话)放在圆括号内,就像正常的函数调用一样。下面是一个带有参数的版本来显示这一点:

// Create a delegate (using System.Action and a no-op lambda)
Action<int> del = i => {};
 
// Call with function style
del(3);
 
// Call with Invoke
del.Invoke(3);

 现在,让我们创建一个非常小的MonoBehaviour,并让Unity 5.2编译它:

using System;
 
using UnityEngine;
 
class TestScript : MonoBehaviour
{
	Action del = () => {};
	Action<int> intDel = i => {};
 
	void Start()
	{
		del();
		del.Invoke();
		intDel(3);
		intDel.Invoke(3);
	}
}

 您可以在项目中找到编译后的DLL:Library/ScriptAssemblies/Assembly-CSharp.dll。现在,让我们将该DLL反编译,看看我们能找到什么不同之处。首先,我们使用ILSpy:

using System;
using UnityEngine;
 
internal class TestScript : MonoBehaviour
{
	private Action del = delegate
	{
	};
 
	private Action<int> intDel = delegate(int i)
	{
	};
 
	private void Start()
	{
		this.del();
		this.del();
		this.intDel(3);
		this.intDel(3);
	}
}

 这看起来与我们的原始源代码几乎完全相同。这里有两个细微的区别:类上有显式的内部(internal)访问说明符,以及使用匿名委托函数而不是对委托字段使用lambdas。主要的区别,也是我们关心的,是delegate如何调用。如您所见,调用版本已被“函数调用”版本替换。我们键入的两个版本都已转换为相同的反编译代码。
为了确保我们的结果,让我们使用dotPeek再次反编译DLL:

internal class TestScript : MonoBehaviour
{
  private Action del;
  private Action<int> intDel;
  [CompilerGenerated]
  private static Action <>f__am$cache2;
  [CompilerGenerated]
  private static Action<int> <>f__am$cache3;
 
  public TestScript()
  {
    if (TestScript.<>f__am$cache2 == null)
    {
      // ISSUE: method pointer
      TestScript.<>f__am$cache2 = new Action((object) null, __methodptr(<del>m__0));
    }
    this.del = TestScript.<>f__am$cache2;
    if (TestScript.<>f__am$cache3 == null)
    {
      // ISSUE: method pointer
      TestScript.<>f__am$cache3 = new Action<int>((object) null, __methodptr(<intDel>m__1));
    }
    this.intDel = TestScript.<>f__am$cache3;
    base..ctor();
  }
 
  private void Start()
  {
    this.del();
    this.del();
    this.intDel(3);
    this.intDel(3);
  }
 
  [CompilerGenerated]
  private static void <del>m__0()
  {
  }
 
  [CompilerGenerated]
  private static void <intDel>m__1(int i)
  {
  }
}

 这个版本比我们从ILSpy得到的输出要长得多,但这种冗长可能会揭示两个委托调用版本之间的差异。我们再次看到该类被显式标记为内部(internal),但委托字段不再是匿名委托函数。相反,我们有私有的静态函数,这些函数被分配给为我们生成的构造函数中的字段。这都比我们编写的代码或ILSpy反编译复杂得多,但在功能上是等效的。当涉及到委托的实际调用时,我们看到了与ILSpy完全相同的情况:Invoke版本已被转换为使用“函数样式”。
最后,让我们看一下使用Microsoft的ILDASM的实际IL汇编代码。为便于阅读,我对其进行了注释:

.method private hidebysig instance void  Start() cil managed
{
  // Code size       47 (0x2f)
  .maxstack  9
 
  // Get the del field and call Invoke() on it
  IL_0000:  ldarg.0
  IL_0001:  ldfld      class [System.Core]System.Action TestScript::del
  IL_0006:  callvirt   instance void [System.Core]System.Action::Invoke()
 
  // Get the del field and call Invoke() on it
  IL_000b:  ldarg.0
  IL_000c:  ldfld      class [System.Core]System.Action TestScript::del
  IL_0011:  callvirt   instance void [System.Core]System.Action::Invoke()
 
  // Get the intDel field and call Invoke(3) on it
  IL_0016:  ldarg.0
  IL_0017:  ldfld      class [mscorlib]System.Action`1<int32> TestScript::intDel
  IL_001c:  ldc.i4.3
  IL_001d:  callvirt   instance void class [mscorlib]System.Action`1<int32>::Invoke(!0)
 
  // Get the intDel field and call Invoke(3) on it
  IL_0022:  ldarg.0
  IL_0023:  ldfld      class [mscorlib]System.Action`1<int32> TestScript::intDel
  IL_0028:  ldc.i4.3
  IL_0029:  callvirt   instance void class [mscorlib]System.Action`1<int32>::Invoke(!0)
 
  // return;
  IL_002e:  ret
} // end of method TestScript::Start

 这里我们看到了相反的情况:两个版本最终都调用了Invoke,而不是使用“函数样式”。似乎两个反编译器都选择将调用版本转换为使用“函数风格”版本的C#代码。
 我们可以得出结论,“函数样式”版本和调用版本编译为同一字节码。它们应该执行相同的功能,使用相同的内存量,并创建完全相同的可执行文件大小。唯一的区别在于调用委托所使用的语法。您更喜欢像调用函数一样调用它们还是使用Invoke方法?请在评论中告诉我是哪一个!

你可能感兴趣的:(Unity)