2010-03-15 24 views
12

Xét đoạn mã sau:C# - đóng cửa trên các trường lớp bên trong trình khởi tạo?

using System; 

namespace ConsoleApplication2 
{ 
    class Program 
    { 
     static void Main(string[] args) 
     { 
      var square = new Square(4); 
      Console.WriteLine(square.Calculate()); 
     } 
    } 

    class MathOp 
    {   
     protected MathOp(Func<int> calc) { _calc = calc; } 
     public int Calculate() { return _calc(); } 
     private Func<int> _calc; 
    } 

    class Square : MathOp 
    { 
     public Square(int operand) 
      : base(() => _operand * _operand) // runtime exception 
     { 
      _operand = operand; 
     } 

     private int _operand; 
    } 
} 

(! Bỏ qua việc thiết kế lớp; Tôi không thực sự viết một máy tính mã này chỉ đại diện cho một repro tối thiểu cho một vấn đề lớn hơn nhiều mà mất một thời gian để thu hẹp)

tôi mong chờ nó đến một trong hai:

  • print "16", hOẶC
  • ném một lỗi thời gian biên dịch nếu đóng cửa trên một trường thành viên không được phép trong trường hợp này

Thay vào đó, tôi nhận được một ngoại lệ vô nghĩa được ném vào dòng được chỉ định. Trên 3.0 CLR, nó là NullReferenceException; trên CLR Silverlight đó là hoạt động khét tiếng có thể làm mất ổn định thời gian chạy.

+0

Nó không biên dịch cho tôi .... "Tham chiếu đối tượng là bắt buộc đối với trường, phương thức hoặc thuộc tính không tĩnh 'ConsoleApplication2 .Square._operand'". Đây có phải là mã chính xác của bạn không? –

+0

Vâng, đó là bản sao/dán và nó biên dịch cho tôi. –

+0

Lưu ý rằng tôi đang ở trên VS2008 - như Aaron lưu ý, nhóm biên dịch 2010 có thể đã phân loại này là lỗi (tức là đã đồng ý với tôi :)) –

Trả lời

11

Nó sẽ không dẫn đến lỗi biên dịch vì nó là đóng tài khoản hợp lệ.

Vấn đề là this chưa được khởi tạo tại thời điểm đóng cửa được tạo. Nhà xây dựng của bạn chưa thực sự chạy khi đối số đó được cung cấp. Vì vậy, kết quả NullReferenceException thực sự là khá hợp lý. Đó là this đó là null!

Tôi sẽ chứng minh điều đó cho bạn. Hãy viết lại mã theo cách này:

class Program 
{ 
    static void Main(string[] args) 
    { 
     var test = new DerivedTest(); 
     object o = test.Func(); 
     Console.WriteLine(o == null); 
     Console.ReadLine(); 
    } 
} 

class BaseTest 
{ 
    public BaseTest(Func<object> func) 
    { 
     this.Func = func; 
    } 

    public Func<object> Func { get; private set; } 
} 

class DerivedTest : BaseTest 
{ 
    public DerivedTest() : base(() => this) 
    { 
    } 
} 

Đoán nội dung này được in? Đúng, đó là true, khoản đóng trả lại nullthis không được khởi chạy khi nó thực thi.

Sửa

Tôi đã tò mò về tuyên bố của Thomas, nghĩ rằng có lẽ họ muốn thay đổi hành vi trong một tiếp theo VS phát hành. Tôi thực sự tìm thấy một số Microsoft Connect issue về điều này. Nó đã được đóng lại là "sẽ không sửa chữa." Odd.

Khi Microsoft nói trong phản hồi của họ, thường không hợp lệ để sử dụng tham chiếu this từ trong danh sách đối số của lệnh gọi hàm dựng cơ bản; tham chiếu đơn giản không tồn tại tại thời điểm đó và bạn sẽ thực sự gặp phải lỗi biên dịch nếu bạn cố gắng sử dụng nó "trần truồng". Vì vậy, được cho là nên tạo ra lỗi biên dịch cho trường hợp đóng, nhưng tham chiếu this bị ẩn khỏi trình biên dịch, ít nhất là trong VS 2008) sẽ phải biết xem cho nó bên trong phần đóng để ngăn chặn mọi người làm điều này. Nó không, đó là lý do tại sao bạn kết thúc với hành vi này.

+0

Bạn đã thử chưa? Tôi nhận được một lỗi biên dịch ... –

+0

@Thomas Levesque: Vâng, tôi đã làm, và nó được biên soạn, và tôi gặp lỗi thời gian chạy tương tự. Tò mò rằng bạn có lỗi biên dịch; Tôi đang ở VS 2008, bạn có tham gia VS 2010 không? Có lẽ họ phân loại này như là một lỗi và cập nhật trình biên dịch để phát hiện điều này? – Aaronaught

+0

+1 Giải thích tốt. Tôi nghi ngờ nó, nhưng không thể chắc chắn thiếu/này/con trỏ trong cửa sổ xem của tôi không chỉ là một quirk VS (tôi thấy nó bị lẫn lộn quá dễ dàng). –

0

Bạn đã thử sử dụng () => operand * operand thay thế? Vấn đề là không có chắc chắn rằng _operand sẽ được thiết lập bởi thời gian bạn gọi là cơ sở. Có, nó đang cố gắng để tạo ra một đóng cửa trên phương pháp của bạn, và không có đảm bảo trật tự của sự vật ở đây.

Vì bạn không đặt _operand, tôi khuyên bạn chỉ nên sử dụng () => operand * operand thay thế.

+1

Nó sẽ làm việc, nhưng nó có một ý nghĩa rất khác nhau ... –

+0

Đủ để nói điều này đánh bại mục đích. Trong mã "thực" của tôi, tôi có một vài MathOps rất phức tạp. Một số bước là phổ biến cho tất cả các MathOps, vì vậy tôi đặt chúng trong lớp cơ sở. Trong một Op cụ thể, phần đầu tiên của phép tính là bất biến - tôi muốn tối ưu hóa nó bằng cách lưu vào bộ đệm kết quả trung gian trong trường thành viên, sau đó cho phép phần còn lại của phép tính (thay đổi dựa trên các tham số để tính toán) tiến hành như bình thường . –

+1

@Richard Berg: Có lẽ bạn có thể giải quyết vấn đề bằng cách sử dụng phương pháp khởi tạo được bảo vệ thay thế? Tôi chắc rằng bạn đã nghĩ về điều đó, nhưng nó không thể làm tổn thương đề cập đến ... – Aaronaught

2

Làm thế nào về điều này:

using System; 
using System.Linq.Expressions; 

namespace ConsoleApplication2 
{ 
    class Program 
    { 
     static void Main(string[] args) 
     { 
      var square = new Square(4); 
      Console.WriteLine(square.Calculate()); 
     } 
    } 

    class MathOp 
    { 
     protected MathOp(Expression<Func<int>> calc) { _calc = calc.Compile(); } 
     public int Calculate() { return _calc(); } 
     private Func<int> _calc; 
    } 

    class Square : MathOp 
    { 
     public Square(int operand) 
      : base(() => _operand * _operand) 
     { 
      _operand = operand; 
     } 

     private int _operand; 
    } 
} 
+0

cách thú vị để trì hoãn giải pháp. Tuy nhiên, vẫn không hoạt động trong năm 2010. – Jimmy

+0

Thông minh. 1 cho sửa chữa nhanh nhất (không cần tái cấu trúc). –

14

Đó là một lỗi biên dịch đã được cố định. Mã này không bao giờ phải là hợp pháp ngay từ đầu, và nếu chúng ta cho phép nó, chúng ta nên có ít nhất mã được tạo ra hợp lệ. Lỗi của tôi. Xin lỗi về sự bất tiện.

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