【发布时间】:2020-07-26 11:22:44
【问题描述】:
在 C# 中,为了以线程安全的方式调用委托,我们可以使用以下代码:
public static void TestInvokeDelegate1()
{
CustomClass cc1 = new CustomClass("cc1");
Action action = cc1.WriteName;
Action action2 = action;
if(action2 != null)
{
action2();
}
}
或者,在 C# 6.0 或更高版本中,我们也可以使用:
public static void TestInvokeDelegate2()
{
CustomClass cc1 = new CustomClass("cc1");
Action action = cc1.WriteName;
action?.Invoke();
}
众所周知,这些代码使用委托的不变性来实现线程安全。但是不变性是如何在 .net 编译器或运行时完成的呢?我使用Ildasm工具获取这两种方法的IL代码:
.method public hidebysig static void TestInvokeDelegate1() cil managed
{
// 32 (0x20)
.maxstack 2
.locals init (class [System.Runtime]System.Action V_0)
IL_0000: ldstr "cc1"
IL_0005: newobj instance void ess_cs.CustomClass::.ctor(string)
IL_000a: ldftn instance void ess_cs.CustomClass::WriteName()
IL_0010: newobj instance void [System.Runtime]System.Action::.ctor(object,
native int)
IL_0015: stloc.0
IL_0016: ldloc.0
IL_0017: brfalse.s IL_001f
IL_0019: ldloc.0
IL_001a: callvirt instance void [System.Runtime]System.Action::Invoke()
IL_001f: ret
} // end of method Program::TestInvokeDelegate1
和,
.method public hidebysig static void TestInvokeDelegate2() cil managed
{
// 32 (0x20)
.maxstack 8
IL_0000: ldstr "cc1"
IL_0005: newobj instance void ess_cs.CustomClass::.ctor(string)
IL_000a: ldftn instance void ess_cs.CustomClass::WriteName()
IL_0010: newobj instance void [System.Runtime]System.Action::.ctor(object,
native int)
IL_0015: dup
IL_0016: brtrue.s IL_001a
IL_0018: pop
IL_0019: ret
IL_001a: callvirt instance void [System.Runtime]System.Action::Invoke()
IL_001f: ret
} // end of method Program::TestInvokeDelegate2
我不精通 IL,但是在这些 IL 代码中,我看到的只是复制对对象的引用。通过简单地复制引用,两个变量都指向同一个对象,不可变性在哪里?没有复制对象数据的代码,它是如何实现的? (也是字符串的不变性)
附加文本:我编写的用于测试字符串和委托的不变性的代码:
(注意:我知道以下代码的 C# 级别的所有内容,它是我编写的,用于测试字符串或委托的不变性。似乎在分配指向它的变量时复制了该对象到另一个变量。但是在IL代码中,我找不到对象复制代码。我想知道的是在编译器或运行时(clr)级别的不变性的内部实现。后面发生了什么?)
public static void TestStringImmutable()
{
string s1 = "abc";
string s2 = s1;
s1 = "ab";
Console.WriteLine(s1); //"ab"
Console.WriteLine(s2); //"abc"
s1 = "abcde";
Console.WriteLine(s1); //"abcde"
Console.WriteLine(s2); //"abc"
}
和,
public static void TestDelegateImmutable()
{
CustomClass cc1 = new CustomClass("cc1");
CustomClass cc2 = new CustomClass("cc2");
Action action = cc1.WriteName;
action += cc2.WriteName;
Action action2 = action;
action -= cc2.WriteName;
action(); //Output: "cc1"
action2(); //Output: "cc1" and "cc2"
action -= cc1.WriteName;
Console.WriteLine(action == null); //true
Console.WriteLine(action2 == null); //false
Console.WriteLine(action2.GetInvocationList().Length); //2
}
CustomClass 类:
class CustomClass
{
private string m_name = null;
public CustomClass(string name)
{
m_name = name;
}
public void WriteName()
{
Console.WriteLine(m_name);
}
}
【问题讨论】:
-
As we know, these codes use the immutability of delegates to achieve thread safety.我不知道。你能给我一些关于这方面的文档吗? -
In C#, for invoking a delegate in a thread-safe style, we can use the following code:“以线程安全的方式”是什么意思? -
是否可以轻松地改变委托的实例?
-
TestStringImmutable绝不证明字符串是不可变的。您在此处用于字符串的代码对于所有类型 的行为方式相同。如果您创建一个对象,将其分配给一个变量,然后将该变量复制到第二个变量,然后将一个新对象(直接)分配给第一个变量(即您正在分配给该变量,而不仅仅是在其上设置一个属性)那么第二个变量将与第一个不同。这不是不变性 - 因为 每个 类型(甚至是可变类型)都是如此。 -
与此类似,
action -= cc2.WriteName;并没有改变分配给它的对象 - 它正在分配一个 new 值。它相当于action = action - cc2.WriteName;。注意分配。 docs.microsoft.com/en-us/dotnet/api/… - 注意它说它返回一个新委托。 docs.microsoft.com/en-us/dotnet/csharp/language-reference/… 另外,请注意,我从未说过任何关于线程安全或不变性的内容。因为它与您提供的代码无关。
标签: c# .net .net-core delegates cil