2012-03-17 52 views
5

tôi có mã này:Resurrection sự khác biệt trong việc sử dụng Object Initializer

Về cơ bản tôi đang cố gắng để chứng minh việc sử dụng C# finalizer và làm cho một đối tượng mà không thể chết, tôi gọi nó là Zombie. Bây giờ, bình thường bản demo này hoạt động rất tốt, nhưng hôm nay tôi đã thử sử dụng cùng một mã với bộ khởi tạo đối tượng thay vì chỉ gán cho thuộc tính (Tên trong trường hợp này). Tôi nhận thấy có sự khác biệt. Cụ thể là các finalizer không bao giờ được gọi là, ngay cả khi tôi đang cố gắng hết sức để làm cho Garbage Collector làm việc đó.

Ai đó có thể giải thích sự khác biệt hay tôi đã tìm thấy lỗi trong trình biên dịch C#?

(Tôi đang sử dụng C# 4 trong VS2010 SP1 trên Win7x64)

Cảm ơn.

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Text; 
using System.Threading; 

namespace Zombie 
{ 
    class Program 
    { 
    static void Main(string[] args) 
    { 
     Console.WriteLine("Main thread: " + Thread.CurrentThread.ManagedThreadId); 

       // case 1: this is where the problem is located. 
     Zombie z = new Zombie { Name = "Guy" }; // object initializer syntax makes that the finalizer is not called. 

       // case 2: this is not causing a problem. The finalizer gets called. 
     //Zombie z = new Zombie(); 
     //z.Name = "Guy"; 

     WeakReference weakZombieGuyRef = new WeakReference(z, true); 

     z = null; 

     GC.GetTotalMemory(forceFullCollection: true); 

     GC.Collect(); 

     while (true) 
     { 

     Console.ReadKey(); 
     if (weakZombieGuyRef.IsAlive) 
     { 
      Console.WriteLine("zombie guy still alive"); 
     } 
     else 
     { 
      Console.WriteLine("Zombie guy died.. silver bullet anyone?"); 
     } 

     Zombie.Instance = null; 

     GC.AddMemoryPressure(12400000); 
     GC.GetTotalMemory(forceFullCollection: true); 

     GC.Collect(); 
     } 


    } 
    } 

    public class Zombie 
    { 
    public string Name { get; set; } 
    public static Zombie Instance = null; 

    ~Zombie() 
    { 
     Console.WriteLine(Thread.CurrentThread.ManagedThreadId); 
     Console.WriteLine("Finalizer called on zombie" + this.Name); 
     lock (typeof(Zombie)) 
     { 
     Instance = this; 

     GC.ReRegisterForFinalize(this); 
     } 
    } 
    } 
} 
+0

Khi tôi tạo Zombie z trong một phương thức bổ sung, thì vấn đề thực sự biến mất. tĩnh công cộng Zombie CreateZombie() { trả về Zombie mới {Name = "Guy"}; } – jeroentrappers

+1

Đã thử điều này với VS2012 và .NET 4.5. Tôi không thể tìm thấy bất kỳ trường hợp nào có sự khác biệt giữa trình khởi tạo đối tượng và mã thuộc tính create-first-then-set-property "kiểu cũ". Nếu bạn làm điều này trong chế độ Debug mà không tối ưu hóa, 'GC' sẽ không thu thập' z' (đó là đối tượng 'z' ban đầu được tham chiếu) trước khi phương thức kết thúc. Đó là để gỡ lỗi dễ dàng hơn; nó sẽ không thu thập cho đến khi biến cục bộ nằm ngoài phạm vi một cách chính thức ('z' vẫn nằm trong phạm vi gần cuối phương thức).Nếu bạn trích xuất phần đầu tiên của 'Main' (với' z') ra thành một phương thức riêng biệt, nó cũng sẽ làm việc dưới dạng xây dựng Debug. –

Trả lời

16

EDIT: Trong khi câu trả lời ban đầu dưới đây vẫn chính xác, có vẻ như đó là hỗn hợp của thông tin debug và tối ưu hóa tạo nên sự khác biệt ở đây.

Từ thí nghiệm của tôi:

Compiler flags      Result 
/o+ /debug-       Finalizer runs 
/o+ /debug+       Finalizer runs 
/o- /debug-       Finalizer runs 
/o- /debug+       Finalizer does *not* run 

Các finalizer vẫn kêu gọi các hộp của tôi, khi biên soạn trên dòng lệnh với /o+. Tôi đoán là bạn đang chạy trong trình gỡ rối - thay đổi hành vi GC. Nếu không có trình gỡ rối, GC sẽ thu thập bất cứ thứ gì mà nó có thể chứng minh sẽ không bao giờ được đọc. Với trình gỡ lỗi, tôi tin rằng GC sẽ không thu thập bất kỳ đối tượng nào vẫn có tham chiếu trên ngăn xếp, ngay cả khi không có mã để đọc các biến được đề cập.

Bây giờ với bộ khởi tạo đối tượng, mã trình biên dịch bao gồm tham chiếu bổ sung trên ngăn xếp. Dòng này:

Zombie z = new Zombie { Name = "Guy" }; 

là một cách hiệu quả:

Zombie tmp = new Zombe(); 
tmp.Name = "Guy"; 
Zombie z = tmp; 

Việc chuyển nhượng để z chỉ được thực hiện sau khi tất cả các thuộc tính đã được thiết lập.

Tôi đoán là biến số tmp ở đây đang giữ đối tượng còn sống.

+0

Cảm ơn câu trả lời cực kỳ nhanh chóng. Tuy nhiên khi tôi chạy có hoặc không có trình gỡ lỗi (F5 hoặc Ctrl-F5) thì không có sự khác biệt. Biến tmp trên stack thực sự sẽ giải thích nó. – jeroentrappers

+0

@jeroentrappers: Bạn có đang xây dựng trong cấu hình Gỡ lỗi hoặc Phát hành không? (Tôi thấy "Finalizer được gọi trên zombieGuy" nếu đó là những gì bạn đang mong đợi nhưng không thấy ...) –

+0

@jeroentrappers: Xem chỉnh sửa của tôi ở đầu bài đăng. –

1

Nếu bạn muốn các đối tượng không chết, bạn thực sự không cần phải gây rối với finalizers. Chỉ cần có một danh sách tĩnh tin của tất cả các trường hợp, và thêm đối tượng vào danh sách đó khi chúng được tạo ra:

class Immortal 
{ 
    static List<Immortal> _immortals = new List<Immortal>(); 

    public Immortal() 
    { 
     _immortals.Add(this); 
    } 
} 
+0

Tôi nhận ra rằng, tuy nhiên mục đích của bản demo là để minh họa cho finalizer. – jeroentrappers

Các vấn đề liên quan