【问题标题】:Does a delegate assignment create a new copy in C#?委托分配是否会在 C# 中创建新副本?
【发布时间】:2015-07-21 05:14:48
【问题描述】:

我阅读了一篇关于 C# 和性能注意事项的文章 (here)

文章中说委托分配会触发内存分配,例如:

每一个局部变量赋值,例如“Func fn = Fn” 在堆上创建委托类 Func 的新实例

我想知道这是否属实,如果属实 - 它是如何实现的?我不熟悉引用分配可以在 C# 中触发额外内存分配的任何方式。

【问题讨论】:

    标签: c# .net memory-management mono garbage-collection


    【解决方案1】:

    文章是对的。很容易测试:

    static void Main(string[] args)
    {
        Action<string[]> main = Main;
        Action<string[]> main2 = Main;
        Console.WriteLine(object.ReferenceEquals(main, main2)); // False
    }
    

    http://ideone.com/dgNxPn

    如果你查看生成的http://goo.gl/S47Wfy 的 IL 代码,很清楚会发生什么:

        IL_0002: ldftn void Test::Main(string[])
        IL_0008: newobj instance void class [mscorlib]System.Action`1<string[]>::.ctor(object, native int)
        IL_000d: stloc.0
        IL_000e: ldnull
    
        IL_000f: ldftn void Test::Main(string[])
        IL_0015: newobj instance void class [mscorlib]System.Action`1<string[]>::.ctor(object, native int)
        IL_001a: stloc.1
    

    所以有两个 newobj instance void class [mscorlib]System.Action1::.ctor(object, native int)`

    请注意,您是对的,这是违反直觉的:

    public class TestEvent
    {
        public event Action Event;
    
        public TestEvent()
        {
            Action d1 = Print;
            Action d2 = Print;
    
            // The delegates are distinct
            Console.WriteLine("d1 and d2 are the same: {0}", object.ReferenceEquals(d1, d2));
    
            Event += d1;
            Event -= d2;
    
            // But the second one is able to remove the first one :-)
            // (an event when is empty is null)
            Console.WriteLine("d2 was enough to remove d1: {0}", Event == null);
        }
    
        public void Print()
        {
            Console.WriteLine("TestEvent");
        }
    }
    

    events 为例,您可以使用“等效但不相同”的委托来移除另一个委托,如示例所示。见https://ideone.com/xeJ6LO

    【讨论】:

    • 那是如何实现的呢?它是运行时支持的吗?我声明的类显然不是这样的。
    • @lysergic-acid 您声明的类的行为方式相同。如果您显式调用new C() 两次,您将获得C 的两个实例。如果您定义一个调用 new C() 的隐式转换运算符,并隐式执行该转换两次,您还将获得两个 C 实例。
    • @hvd 我猜这段代码中的问题是你没有明确地调用 new C() 两次。所以这是编译器添加的某种糖,它调用 new 两次,因此有 2 个不同的实例。
    • @lysergic-acid msdn.microsoft.com/en-us/library/ms173176.aspx 在 C# 1.0 和 1.2 中,您必须明确地执行 new MyDelegate(function)。然后在 C# 2.0 中,他们将其隐式化。我要补充一点,即使该表单也是假的,因为委托的构造函数的签名是MyDelegate(object, IntPtr),其中object 是对委托的“目标”对象的引用(this 将是由委托方法使用),IntPtr 是方法的地址。你可以看看newobj在IL代码中是如何被调用的(见第二个代码块)
    • @lysergic-acid 这就是我在评论中已经提到隐式转换的原因。如果您对自己的类使用隐式转换,则调用代码可以获得int i = 1; C c1 = i; C c2 = i;(没有任何显式new C()s),但也可以获得!object.ReferenceEquals(c1, c2)。事实上,即使隐式转换为内置类型也会发生这种情况:int i = 1; object o1 = i; object o2 = i; 现在object.ReferenceEquals(o1, o2) 报告了什么?
    【解决方案2】:

    显然,您正在声明一个新的委托实例并将其初始化:

    Func fn=new Func(Fn);  // hidden by syntactic sugar
    

    但是这篇文章在更高的内存使用方面具有误导性,它和其他文章一样,只是从来没有被垃圾收集,因为它的数量太少了。

    【讨论】:

    • 我以为它只是创建了对同一个委托的另一个引用
    • 什么代表? Fn 是一个函数。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-10-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-05-25
    相关资源
    最近更新 更多