2008-12-15 4 views
15

Khi bạn có mã như sau:Tại sao trình biên dịch C# phát ra Activator.CreateInstance khi gọi mới với một kiểu generic với một ràng buộc mới()?

static T GenericConstruct<T>() where T : new() 
{ 
    return new T(); 
} 

Các biên dịch C# khăng khăng phát ra một lời kêu gọi Activator.CreateInstance, mà là chậm hơn đáng kể so với một constructor bản địa.

Tôi có cách giải quyết như sau:

public static class ParameterlessConstructor<T> 
    where T : new() 
{ 
    public static T Create() 
    { 
     return _func(); 
    } 

    private static Func<T> CreateFunc() 
    { 
     return Expression.Lambda<Func<T>>(Expression.New(typeof(T))).Compile(); 
    } 

    private static Func<T> _func = CreateFunc(); 
} 

// Example: 
// Foo foo = ParameterlessConstructor<Foo>.Create(); 

Nhưng nó không có ý nghĩa với tôi lý do tại sao việc này nên cần thiết.

+0

Tôi cũng nhận thấy điều tương tự ... nhưng tôi không biết tại sao. –

+0

Tôi đang sử dụng trình biên dịch đoạn mã và trình biên dịch không ném bất kỳ lỗi nào. Ngoài ra, hàm tạo được gọi khi hàm T() mới được gọi. – shahkalpesh

+1

@shahkalpesh: Không ai nói rằng sẽ có lỗi. Vấn đề là Activator.CreateInstance chậm hơn so với biểu mẫu đại biểu. –

Trả lời

9

I nghi ngờ đó là sự cố JITting. Hiện tại, JIT sử dụng lại cùng một mã được tạo cho tất cả các đối số kiểu tham chiếu - do đó, các điểm vtable của List<string> có cùng mã máy giống như của List<Stream>. Điều đó sẽ không hoạt động nếu mỗi cuộc gọi new T() phải được giải quyết trong mã JITted.

Chỉ cần đoán, nhưng nó làm cho một số tiền là nhất định.

Một điểm nhỏ thú vị: trong không phải là trường hợp hàm tạo tham số không có kiểu giá trị được gọi, nếu có một giá trị (hiếm khi biến mất). Xem my recent blog post để biết chi tiết. Tôi không biết liệu có cách nào ép buộc nó trong cây biểu cảm hay không.

8

Điều này có thể do không rõ T có phải là loại giá trị hoặc loại tham chiếu hay không. Việc tạo ra hai loại này trong một kịch bản phi chung chung tạo ra IL rất khác nhau. Khi đối mặt với sự mơ hồ này, C# buộc phải sử dụng phương pháp tạo kiểu phổ biến. Activator.CreateInstance khớp với hóa đơn.

Thử nghiệm nhanh xuất hiện để hỗ trợ ý tưởng này. Nếu bạn gõ mã sau đây và kiểm tra IL, nó sẽ sử dụng initobj thay vì CreateInstance vì không có sự mơ hồ về kiểu.

static void Create<T>() 
    where T : struct 
{ 
    var x = new T(); 
    Console.WriteLine(x.ToString()); 
} 

Chuyển đổi thành lớp và ràng buộc mới() mặc dù vẫn buộc một Activator.CreateInstance.

+5

Tôi đoán câu hỏi tiếp theo ngay lập tức sẽ là "tại sao không có một hướng dẫn IL thích hợp để tạo một thể hiện của một loại chung với một ràng buộc thích hợp?" Nó không giống như họ không thể đã xây dựng mà ngay từ đầu :) –

+0

Đồng ý nó thực sự có vẻ như họ thực hiện một API thay vì một chỉ dẫn IL. Nhận xét trên trang tài liệu MSDN cho Activator.CreateInstance đặc biệt nói rằng nó nên được gọi cho kịch bản này. Lựa chọn kỳ quặc, tôi chắc chắn có một lý do chính đáng. – JaredPar

+0

Tôi nghi ngờ lý do là để tăng chia sẻ mã JIT'd. Nếu bạn có cuộc gọi trực tiếp tới hàm tạo của một loại trong mã JIT'd, thì bạn không thể chia sẻ mã JIT'd đó với một phiên bản khác cho một loại khác, ví dụ: 'T Tạo <T>() trong đó T: new() {return new T();}' sẽ chia sẻ mã máy cho Tạo < chuỗi >() và Tạo <ArrayList>(). – jonp

2

Thú vị quan sát :)

Đây là một biến thể đơn giản hơn về giải pháp của bạn:

static T Create<T>() where T : new() 
{ 
    Expression<Func<T>> e =() => new T(); 
    return e.Compile()(); 
} 

Rõ ràng ngây thơ (và có thể chậm) :)

+2

Tôi không nghĩ rằng nó sẽ làm việc, bởi vì nó đặc biệt là "mới T()" rằng workaround của ông đang cố gắng tránh. –

+1

@ Jelel Mueller Trên thực tế nó hoạt động. Cây biểu thức chứa NewExpression ở đây. – ghord

+1

Có, đó là Biểu thức của Func , không phải là Func . "() => New T()" không tạo ra IL (do đó tạo ra Activator.CreateInstance()), nhưng một cây biểu thức mà lần lượt được biên dịch trong thời gian chạy khi T được biết. Vấn đề duy nhất ở đây là mỗi khi bạn gọi hàm này, bạn biên dịch lại câu lệnh này. –

3

Tại sao việc này cần thiết?

Vì ràng buộc chung mới() đã được thêm vào C# 2.0 trong .NET 2.0.

Biểu thức <T> và bạn bè, trong khi đó, đã được thêm vào .NET 3.5.

Vì vậy, giải pháp thay thế của bạn là cần thiết vì không thể thực hiện được trong .NET 2.0. Trong khi đó, (1) sử dụng Activator.CreateInstance() là có thể, và (2) IL thiếu một cách để thực hiện 'T mới()', vì vậy Activator.CreateInstance() được sử dụng để thực hiện hành vi đó.

2

Đây là một chút nhanh hơn, kể từ khi biểu hiện chỉ biên soạn lần:

public class Foo<T> where T : new() 
{ 
    static Expression<Func<T>> x =() => new T(); 
    static Func<T> f = x.Compile(); 

    public static T build() 
    { 
     return f(); 
    } 
} 

Phân tích hiệu quả hoạt động, phương pháp này chỉ là nhanh như biểu thức nhiều tiết biên soạn và nhiều hơn, nhanh hơn nhiều so new T() (Nhanh hơn 160 lần trên PC thử nghiệm của tôi).

Để có hiệu suất tốt hơn một chút, cuộc gọi phương thức xây dựng có thể được loại bỏ và hàm functor có thể được trả lại thay vào đó, ứng dụng khách có thể lưu vào bộ nhớ cache và gọi trực tiếp.

public static Func<T> BuildFn { get { return f; } }