2010-10-11 23 views
15

mam trochę starszych kod z metodą foo który ma 700 + przeciążeń:C# nie można nazwać przeciążone metody nierodzajową z metody rodzajowe

[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... 

Chciałbym narazić te przeciążone metody poprzez pojedynczy metoda z zastosowaniem leków generycznych:

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... 
} 

Niestety ta zwraca błędy: "The best przeciążony metodą dopasowania dla 'foo (int, ref StructA)' ma pewne nieprawidłowe argumenty" i "nie można przekonwertować z "ref T" na "ref StructA" ".

Czy istnieje elegancki sposób, aby to osiągnąć?

+0

Czy typy "classA", "classB" są częścią hierarchii klas? Jeśli tak, to czy możesz wyjaśnić strukturę? – Oded

+0

700 przeciążeń? Dość duże dla klasy. – TalentTuner

+0

Uhu, 700 przeciąża? Na pewno chcesz dodać do tego kolejną warstwę złożoności? – Makach

Odpowiedz

8

Miałem nadzieję, że pomoże tutaj dynamic, ale nie podoba mi się ref. W każdym razie, odbicie powinno działać:

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; 
} 

Oto zoptymalizowana wersja, że ​​tylko robi odbicie raz; powinny być znacznie szybciej:

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

To działa dobrze na przykładowe metody, ale niestety nie dla metod statycznych tj. Chronione statyczne void foo (int len, ref classA obj) {}, pojawia się błąd System.TypeInitializationException. (BindingFlags zostały zmienione z .Instance na .Static już) –

+0

@sprocketonline - aby poradzić sobie ze statycznymi, musisz wyjąć arg0; są statyczne? A może jest mieszanka obu? –

+0

wszystkie są statyczne - wszystkie są wywołaniami P/Invoke do zewnętrznej biblioteki C. –

5

pierwsze - skoro masz where T : new()
można po prostu stwierdzić T obj = new T(); zamiast T obj = Activator.CreateInstance<T>();
Teraz do drugiej kwestii, mają wiele funkcji, jak to w jednej klasie jest chaos.
chciałbym zdefiniować interfejs

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

i dokonać wszystkich klas wdrożyć. A potem:

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

Obawiam się, że nie można użyć rodzajowych w sposób, który chcesz tutaj. Powodem jest to, że ogólna metoda musi zostać skompilowana do IL i musi rozwiązać problem z przeciążeniem w czasie kompilacji. W tym momencie nie wie, które przeciążenie wybrać, ponieważ jest to informacja o środowisku wykonawczym.

Jeśli masz tak dużo przeciążeń, jak mówisz, to naprawdę rozważałbym zastosowanie lepszej abstrakcji. Na przykład zaimplementuj swoją metodę foo jako członka jakiegoś interfejsu implementowanego przez wszystkie klasy. Jeśli podasz więcej szczegółów, jestem pewien, że tutaj ludzie mogą udzielać porad dotyczących lepszego projektu.

Jeśli naprawdę potrzebujesz zrobić to w ten sposób, możesz prawdopodobnie użyć czegoś takiego jak Dictionary<Type, SomeDelegate<int, obj> i przechowywać wszystkie metody foo w słowniku. Metoda callFoo po prostu przeprowadzić wyszukiwanie:

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

Wtedy jedyny problem byłby, jak dodać je wszystkie do słownika. Prawdopodobnie możesz to zrobić po prostu ręcznie, w statycznym konstruktorze każdej klasy lub dynamicznie używając refleksji.

5

Można to zrobić poprzez dbanie o Organizowanie siebie zamiast pozostawienia go do P/Invoke naziemnego.Redeclare foo tak:

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

które teraz pozwala określić ogólną metodę:

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); 
     } 
    } 

Jeśli perf jest krytyczna następnie można ją przyspieszyć przez utrzymywanie pamięci przydzielonej przez AllocCoTaskMem dookoła, coraz to tylko w razie potrzeby. Z twojego pytania nie wynika jednoznacznie, czy funkcje C aktualizują przekazaną strukturę, możesz pominąć wywołanie PtrToStructure, jeśli tego nie zrobi.

+0

Tak, funkcja C aktualizuje przekazaną strukturę. To rozwiązanie wygląda elegancko, ale niestety powoduje następujący błąd: "System.AccessViolationException: Próba odczytu lub zapisu chronionej pamięci. Często jest to wskazówką, że inna pamięć jest uszkodzona." –

+0

Hmm, powinien zadziałać. Czy te struktury lub klasy przechodzisz? Jeśli przekazujesz obiekty klasy, potrzebujesz wskaźnika do wskaźnika, ref IntPtr. –

+0

Dobra uwaga - to byłyby struktury, które przechodzę. Zaktualizowałem to pytanie, aby to odzwierciedlić. –

Powiązane problemy