【问题标题】:How the immutability of C# delegates (and strings) is implemented at the bottom?C# 委托(和字符串)的不变性是如何在底层实现的?
【发布时间】: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


【解决方案1】:

看看我的解释:

public static void TestDelegateImmutable()
{
    CustomClass cc1 = new CustomClass("cc1");
    CustomClass cc2 = new CustomClass("cc2");

    Action action = cc1.WriteName; //Allocate action to an object refers to cc1.WriteName.
    action += cc2.WriteName; //Allocate action to an new object refers to cc1.WriteName and cc2.WriteName.
    Action action2 = action; //Allocate action2 to the object of action has been created above.
    action -= cc2.WriteName; //Allocate action to an new object refers to cc1.WriteName.

    action(); //Output: "cc1"
    action2(); //Output: "cc1" and "cc2"

    action -= cc1.WriteName; //Allocate action to null.
    Console.WriteLine(action == null); //true
    Console.WriteLine(action2 == null); //false
    Console.WriteLine(action2.GetInvocationList().Length); //2
}

希望对你有意义。

【讨论】:

  • 您的代码对我来说没有任何意义,它与我提出的问题无关。
  • @DingXin 阅读我上面的 cmets,然后重新阅读此答案。因为这个答案是正确的。
猜你喜欢
  • 1970-01-01
  • 2019-10-07
  • 1970-01-01
  • 2013-03-08
  • 1970-01-01
  • 2020-09-11
  • 1970-01-01
  • 2018-06-11
  • 1970-01-01
相关资源
最近更新 更多