2010-10-11 36 views
15

Tôi có một số mã di sản với một phương pháp foo trong đó có 700 quá tải:C# không thể gọi quá tải phương pháp phi generic từ phương pháp chung

[DllImport("3rdparty.dll")] 
protected static extern void foo(int len, ref structA obj); 
[DllImport("3rdparty.dll")] 
protected static extern void foo(int len, ref structB obj); 
[DllImport("3rdparty.dll")] 
protected static extern void foo(int len, ref structC obj); 
//and 700 similar overloads for foo... 

Tôi muốn để lộ những phương pháp quá tải qua một đơn phương pháp sử dụng Generics:

public void callFoo<T>(int len) 
    where T : new() //ensure an empty constructor so it can be activated 
{ 
    T obj = Activator.CreateInstance<T>(); //foo expects obj to be empty, and fills it with data 
    foo(len, ref obj); 

    //...do stuff with obj... 
} 

Thật không may này sẽ trả về lỗi: "các quá tải tốt nhất phù hợp với phương pháp 'foo (int, ref StructA) có một số đối số không hợp lệ" và "không thể chuyển đổi từ 'ref T' thành 'ref StructA' ".

Có cách nào thanh lịch để đạt được điều này không?

+0

Các loại 'classA',' classB' là một phần của phân cấp lớp? Nếu vậy, bạn có thể giải thích cấu trúc? – Oded

+0

700 quá tải? Khá lớn cho một lớp học. – TalentTuner

+0

Uhu, 700 quá tải? Bạn có chắc chắn muốn thêm một lớp phức tạp khác vào đó không? – Makach

Trả lời

8

Tôi đã hy vọng rằng dynamic sẽ trợ giúp ở đây, nhưng không giống như ref. Dù sao, phản ánh nên làm việc:

public T callFoo<T>(int len) 
    where T : new() //ensure an empty constructor so it can be activated 
{ 
    T obj = new T(); 
    GetType().GetMethod("foo", BindingFlags.Instance | BindingFlags.NonPublic, 
     null, new[] { typeof(int), typeof(T).MakeByRefType() }, null) 
     .Invoke(this, new object[] { len, obj }); 
    return obj; 
} 

Dưới đây là một phiên bản tối ưu hóa mà chỉ thực hiện sự phản xạ một lần; nên nhiều nhanh hơn:

class Test 
{ 

    protected void foo(int len, ref classA obj){} 
    protected void foo(int len, ref classB obj){ } 
    protected void foo(int len, ref classC obj){} 
    static readonly Dictionary<Type, Delegate> functions; 
    delegate void MyDelegate<T>(Test arg0, int len, ref T obj); 
    static Test() 
    { 
     functions = new Dictionary<Type, Delegate>(); 
     foreach (var method in typeof(Test).GetMethods(BindingFlags.NonPublic | BindingFlags.Instance)) 
     { 
      if (method.Name != "foo") continue; 
      var args = method.GetParameters(); 
      if (args.Length != 2 || args[0].ParameterType != typeof(int)) continue; 
      var type = args[1].ParameterType.GetElementType(); 
      functions[type] = Delegate.CreateDelegate(
       typeof(MyDelegate<>).MakeGenericType(type), method); 
     } 
    } 
    public T callFoo<T>(int len) 
     where T : new() //ensure an empty constructor so it can be activated 
    { 
     T obj = new T(); 
     Delegate function; 
     if (!functions.TryGetValue(typeof(T), out function)) throw new NotSupportedException(
      "foo is not supported for " + typeof(T).Name); 
     ((MyDelegate<T>)function)(this, len, ref obj); 
     return obj; 
    } 
} 
+0

Điều này làm việc tốt cho các phương pháp ví dụ, nhưng tiếc là không cho các phương thức tĩnh, tức là bảo vệ static void foo (int len, ref classA obj) {}, tôi nhận được một lỗi System.TypeInitializationException. (các BindingFlags đã được thay đổi từ .Instance to .Static rồi) –

+0

@sprocketonline - để xử lý tĩnh bạn sẽ cần phải lấy ra arg0; tất cả đều tĩnh? Hoặc là có một hỗn hợp của cả hai? –

+0

tất cả đều tĩnh - tất cả chúng đều là P/Gọi các cuộc gọi đến thư viện C bên ngoài. –

5

Đệ Nhất - kể từ khi bạn có where T : new()
bạn chỉ có thể nêu T obj = new T(); thay vì T obj = Activator.CreateInstance<T>();
Bây giờ, đối với các vấn đề khác, có rất nhiều chức năng như thế này trong một lớp là hỗn loạn.
tôi sẽ xác định một giao diện

public interface IFoo 
{ 
    void foo(int len); 
} 

và làm cho tất cả các lớp thực hiện nó. Và sau đó:

public void callFoo<T>(int len) 
    where T : IFoo, new() //ensure an empty constructor so it can be activated 
{ 
    T obj = new T(); 
    obj.foo(len); 
} 
2

Tôi sợ rằng bạn không thể sử dụng Generics theo cách bạn muốn ở đây. Lý do là phương pháp chung cần phải được biên dịch sang IL và nó cần phải giải quyết tình trạng quá tải tại thời gian biên dịch. Tại thời điểm đó, nó không thực sự biết quá tải để chọn, bởi vì đây là thông tin thời gian chạy.

Nếu bạn có quá nhiều tình trạng quá tải như bạn nói, thì tôi thực sự xem xét sử dụng một số trừu tượng tốt hơn. Ví dụ: triển khai phương thức foo của bạn làm thành viên của một số giao diện được thực hiện bởi tất cả các lớp. Nếu bạn cung cấp thêm chi tiết, tôi chắc chắn mọi người ở đây có thể đưa ra lời khuyên về thiết kế tốt hơn.

Nếu bạn thực sự cần thực hiện theo cách này, thì bạn có thể sử dụng một số thứ như Dictionary<Type, SomeDelegate<int, obj> và lưu trữ tất cả các phương thức foo trong từ điển. Phương pháp callFoo chỉ cần thực hiện tra cứu:

public void callFoo<T>(int len) where T : new() 
{ 
    T obj = Activator.CreateInstance<T>(); 
    fooDictionary[typeof(T)](len, obj); 
    // ... 
} 

Sau đó, vấn đề duy nhất là, cách thêm tất cả chúng vào từ điển. Bạn có thể có thể làm điều đó chỉ đơn giản bằng tay, trong hàm dựng tĩnh của mỗi lớp hoặc tự động sử dụng sự phản chiếu.

5

Bạn có thể làm điều này bằng cách chăm sóc của marshaling mình thay vì để nó vào P/Gọi marshaller.foo redeclare như thế này:

[DllImport("3rdparty.dll")] 
    private static extern void foo(int len, IntPtr obj); 

Mà bây giờ cho phép bạn xác định một phương pháp chung:

protected void foo<T>(ref T obj) { 
     int len = Marshal.SizeOf(obj); 
     IntPtr mem = Marshal.AllocCoTaskMem(len); 
     try { 
      Marshal.StructureToPtr(obj, mem, false); 
      foo(len, mem); 
      // Optional: 
      obj = (T)Marshal.PtrToStructure(mem, typeof(T)); 
     } 
     finally { 
      Marshal.FreeCoTaskMem(mem); 
     } 
    } 

Nếu Perf là ​​rất quan trọng thì bạn có thể tăng tốc độ nó lên bằng cách giữ cho bộ nhớ được phân bổ bởi AllocCoTaskMem xung quanh, phát triển nó chỉ khi cần thiết. Nó không phải là rõ ràng từ câu hỏi của bạn cho dù các chức năng C cập nhật cấu trúc được thông qua, bạn có thể bỏ qua các cuộc gọi PtrToStructure nếu nó không.

+0

Có, hàm C cập nhật cấu trúc đã truyền. Giải pháp này trông thanh lịch, nhưng tiếc là kết quả trong các lỗi sau: "System.AccessViolationException: Cố gắng đọc hoặc ghi bộ nhớ được bảo vệ. Đây thường là một dấu hiệu cho thấy bộ nhớ khác bị hỏng." –

+0

Hmm, nên hoạt động. Những cấu trúc hoặc lớp học mà bạn đang đi qua? Nếu bạn vượt qua các đối tượng lớp thì bạn cần một con trỏ trỏ tới một con trỏ, là IntPtr. –

+0

Điểm tốt - nó sẽ là cấu trúc tôi đang đi qua. Tôi đã cập nhật câu hỏi để phản ánh điều đó. –

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