2010-10-28 22 views
19

Tôi đang viết một lớp về cơ bản cùng một loại phép tính cho từng loại số nguyên thủy trong C#. Mặc dù tính toán thực tế phức tạp hơn, nhưng hãy nghĩ nó là phương pháp tính trung bình của một số giá trị, ví dụ:Mã C# chung và toán tử cộng tác

class Calc 
{ 
    public int Count { get; private set; } 
    public int Total { get; private set; } 
    public int Average { get { return Count/Total; } } 
    public int AddDataPoint(int data) 
    { 
     Total += data; 
     Count++; 
    } 
} 

Bây giờ để hỗ trợ mà hoạt động tương tự cho đôi, phao và các lớp học có lẽ khác mà xác định toán tử + và nhà điều hành /, suy nghĩ đầu tiên của tôi là chỉ cần sử dụng Generics:

class Calc<T> 
{ 
    public T Count { get; private set; } 
    public T Total { get; private set; } 
    public T Average { get { return Count/Total; } } 
    public T AddDataPoint(T data) 
    { 
     Total += data; 
     Count++; 
    } 
} 

Đáng tiếc là C# là không thể xác định xem T có hỗ trợ toán tử + và/do đó không biên dịch đoạn mã trên hay không. Suy nghĩ tiếp theo của tôi là ép buộc T thành các loại hỗ trợ các toán tử đó, nhưng nghiên cứu ban đầu của tôi cho thấy điều này không thể thực hiện được.

Chắc chắn có thể đóng hộp từng loại tôi muốn hỗ trợ trong lớp thực hiện giao diện tùy chỉnh, ví dụ: IMath và hạn chế T cho điều đó, nhưng mã này sẽ được gọi là một số lượng lớn thời gian và tôi muốn tránh boxing trên không.

Có cách nào thanh lịch và hiệu quả để giải quyết vấn đề này mà không cần sao chép mã không?

+1

Bạn không thể sử dụng LINQ? Tổng và Trung bình được hỗ trợ trên IEnumerable. – Mathias

+2

Xem câu hỏi SO khác này: http://stackoverflow.com/questions/32664/c-generic-constraint-for-only-integers – spinon

+0

@Mathias: Không, tôi đã sử dụng điều này làm ví dụ đơn giản về tính toán thực tế. –

Trả lời

13

Tôi đã kết thúc bằng cách sử dụng Biểu thức, một phương pháp được vạch ra bởi Marc Gravell mà tôi tìm thấy bằng cách theo các liên kết ngoài nhận xét của spinon.

http://www.yoda.arachsys.com/csharp/genericoperators.html

+0

Chỉ cần chắc chắn rằng bạn nhớ cache các đại biểu và tái sử dụng chúng, p –

+3

Cũng lưu ý rằng MiscUtils có lớp 'Operator' được xây dựng sẵn cung cấp tất cả các phương thức tiện ích cần thiết mà bạn có thể cần. –

+0

@Marc: Có, tôi đã thực hiện nhận xét của bạn ;-) –

2

Có một phương pháp sử dụng năng động trong C# 4.0, nó không phải là hoàn hảo rõ ràng nhưng nó có thể mang lại một ánh sáng mới đến vấn đề này.

Cụ thể in this blog post

+0

Vấn đề với 'năng động' là nó giới thiệu một mức độ khác biệt. Tôi đang thực hiện một số lượng lớn các tính toán phức tạp và nghi ngờ (nhưng không biết, phải trung thực) rằng sự gián đoạn sẽ có tác động có thể đo lường về hiệu suất ứng dụng tổng thể. –

1

tôi tìm thấy một cách tiếp cận thú vị, đó là dễ dàng hơn để mã và gỡ lỗi hơn so với giải pháp cây biểu hiện ban đầu tôi đã sử dụng:

http://www.codeproject.com/KB/cs/genericnumerics.aspx

giải pháp này sử dụng các ràng buộc kiểu chung chung trong một cách thú vị để đảm bảo tất cả các hoạt động được yêu cầu được hỗ trợ, nhưng không giới thiệu bất kỳ cuộc gọi phương thức nào hoặc gọi phương thức ảo.

4

(xin lỗi nếu tôi đăng nó ngày hôm nay, nhưng tôi đang tìm kiếm một nơi để đặt đoạn mã này, và câu hỏi này dường như là hoàn hảo)

Như một phần mở rộng về bài viết của Gravell:

public static class Add<T> 
{ 
    public static readonly Func<T, T, T> Do; 

    static Add() 
    { 
     var par1 = Expression.Parameter(typeof(T)); 
     var par2 = Expression.Parameter(typeof(T)); 

     var add = Expression.Add(par1, par2); 

     Do = Expression.Lambda<Func<T, T, T>>(add, par1, par2).Compile(); 
    } 
} 

Bạn sử dụng nó như:

int sum = Add<int>.Do(x, y); 

Ưu điểm là chúng tôi sử dụng hệ thống loại NET cho bảo vệ an toàn các "biến thể" khác nhau của Add và creat ing những cái mới nếu cần thiết. Vì vậy, lần đầu tiên bạn gọi Add<int>.Do(...), Expression sẽ được tạo, nhưng nếu bạn gọi nó lần thứ hai, thì Add<int> sẽ được khởi tạo hoàn toàn.

Trên một số điểm chuẩn đơn giản, nó chậm hơn 2x so với bổ sung trực tiếp. Tôi nghĩ nó rất tốt. Ah ... nó tương thích với các đối tượng xác định lại số operator+. Rõ ràng việc xây dựng các hoạt động khác rất dễ dàng.

Addition từ Meirion Hughes

Phương pháp có thể được mở rộng với meta-mã hóa, do đó bạn có thể xử lý các trường hợp T1hoạt độngT2. Ví dụ, ở đây nếu T1 là một số, sau đó nó cần phải được chuyển đổi thành T2 == double trước tiên trước khi operator * sau đó chuyển đổi nó trở lại. Trong khi khi T1FooFoo có toán tử nhân với T2 == double bạn có thể bỏ qua chuyển đổi. Cần có try, catch vì đây là cách dễ nhất để kiểm tra xem có T operator *(T, double) không.

public static class Scale<T> 
{ 
    public static Func<T, double, T> Do { get; private set; } 

    static Scale() 
    { 
     var par1 = Expression.Parameter(typeof(T)); 
     var par2 = Expression.Parameter(typeof(double)); 

     try 
     { 
      Do = Expression 
       .Lambda<Func<T, double, T>>(
        Expression.Multiply(par1, par2), 
        par1, par2) 
       .Compile(); 
     } 
     catch 
     { 
      Do = Expression 
       .Lambda<Func<T, double, T>>(
        Expression.Convert(
         Expression.Multiply(
          Expression.Convert(par1, typeof (double)), 
          par2), 
         typeof(T)), 
        par1, par2) 
       .Compile(); 
     } 
    } 
} 
+0

@Meirion Tôi không thích những gì bạn đã làm (thêm bằng cách chỉnh sửa) ... Nhưng mã đã được gọn gàng. Tôi đã chỉnh sửa lại một chút và thay đổi một chút lời giải thích – xanatos

+0

vâng tôi biết, xin lỗi. Tôi nghĩ rằng nó sẽ hữu ích (nó đã cho tôi), và tôi đã được thêm nó như là câu trả lời riêng biệt, nhưng thread này bị khóa và thread khác sẽ được off-topic. :/ –

+0

@MeirionHughes Chỉ trong thời gian tới, hãy đặt tên của bạn vào phần văn bản được thêm vào, giống như tôi đã làm * Bổ sung từ Meirion Hughes *, vì vậy rõ ràng tác giả là gì và tác giả 2 là gì – xanatos