2010-10-28 17 views
19

Piszę klasę, która wykonuje zasadniczo ten sam typ obliczenia dla każdego z podstawowych typów liczbowych w języku C#. Chociaż rzeczywiste obliczenia są bardziej złożone, należy je traktować jako metodę obliczania średniej z wielu wartości, np.Ogólny kod C# i operator Plus

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

Teraz wspierać tę samą operację dla podwójnym, pływaka i być może innych klas, które określają operatora + i operatora /, moja pierwsza myśl była po prostu użyć rodzajowych:

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

Niestety C# jest w stanie ustal, czy T obsługuje operatorów + i/lub nie kompiluje powyższego fragmentu. Następną myślą było ograniczenie T do typów, które wspierają tych operatorów, ale moje wstępne badania wskazują, że nie można tego zrobić.

Z pewnością możliwe jest umieszczenie każdego z typów, które chcę obsłużyć w klasie implementującej niestandardowy interfejs, np. IMath i ogranicz T do tego, ale ten kod będzie się nazywał wielką liczbę razy i chcę uniknąć narzutów boksu.

Czy istnieje elegancki i skuteczny sposób rozwiązania tego problemu bez duplikowania kodu?

+1

Nie możesz po prostu użyć LINQ? Suma i średnia są obsługiwane w IEnumerable. – Mathias

+2

Sprawdź to inne pytanie ogólne: http://stackoverflow.com/questions/32664/c-generic-constraint-for-only-integers – spinon

+0

@Mathias: Nie, użyłem tego jako uproszczonego przykładu rzeczywistych obliczeń. –

Odpowiedz

13

Skończyło się na wykorzystaniu Wyrażeń, podejścia opisanego przez Marca Gravella, które znalazłem przez podążanie za linkami do komentarzy spinona.

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

+0

Tylko pamiętaj, aby buforować delegatów i używać ich ponownie: p –

+3

Należy również pamiętać, że MiscUtils ma wbudowaną klasę 'Operator', która zapewnia wszystkie niezbędne narzędzia, których prawdopodobnie będziesz potrzebować. –

+0

@Marc: Tak, zaimplementowałem twój komentarz ;-) –

2

Jest to podejście za pomocą dynamicznych w C# 4.0, to oczywiście nie jest doskonały, ale może przynieść nowe światło na sprawę.

Szczegóły są in this blog post

+0

Kłopot z "dynamicznym" polega na tym, że wprowadza on inny poziom pośrednictwa. Przeprowadzam wiele skomplikowanych obliczeń i podejrzewam (ale nie wiem, szczerze mówiąc), że pośrednictwo będzie miało wymierny wpływ na ogólną wydajność aplikacji. –

1

znalazłem kolejny ciekawy podejście, które jest łatwiejsze do kodu i debugowania niż roztwór drzewa wyrażenie I pierwotnie używany:

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

Rozwiązanie to wykorzystuje ograniczenia typu rodzajowego w Ciekawy sposób na zapewnienie obsługi wszystkich wymaganych operacji, ale bez wprowadzania jakichkolwiek boksów lub wywołań metod wirtualnych.

4

(wybaczcie jeśli pisać to dzisiaj, ale szukałem miejsca, gdzie umieścić ten fragment kodu, a to pytanie wydawała się być idealny)

Jako rozszerzenie na artykule Gravell za:

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

go używać jak:

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

zaletą jest to, że możemy korzystać z systemu typu .NET na przechowanie różne „warianty” Add i cREAT w razie potrzeby nowe. Dlatego po raz pierwszy zadzwonisz pod numer Add<int>.Do(...), zostanie utworzony Expression, ale jeśli zadzwonisz po raz drugi, Add<int> zostanie już w pełni zainicjowany.

W przypadku niektórych prostych testów jest dwa razy wolniejsze niż dodawanie bezpośrednie. Myślę, że to bardzo dobrze. Ah ... jest kompatybilny z obiektami, które na nowo definiują operator+. Wyraźnie budowanie innych operacji jest łatwe.

Dodatek z Meirion Hughes

metoda może być rozszerzony o meta-kodowania, dzięki czemu można obsługiwać przypadki T1pracyT2. Na przykład tutaj, jeśli T1 jest liczbą, to należy ją przekonwertować na T2 == double najpierw przed operator *, a następnie ją przekonwertować. Podczas gdy T1 jest Foo i Foo ma operator do pomnożenia z T2 == double można pominąć konwersji. Konieczne jest użycie try, catch, ponieważ jest to najprostszy sposób sprawdzenia, czy obecny jest T operator *(T, double).

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

@Mirion Nie podoba mi się to, co zrobiłeś (dodając, edytując) ... Ale kod był zgrabny. Ponownie zredagowałem trochę i zmieniłem trochę wyjaśnienie: – xanatos

+0

Tak, wiem, przepraszam. Pomyślałem, że byłoby to użyteczne (było to dla mnie), i miałem zamiar dodać je jako oddzielną odpowiedź, ale ten wątek jest zablokowany, a drugi wątek będzie poza tematem. :/ –

+0

@MeirionHughes Następnym razem, umieść swoje imię na dodanym fragmencie tekstu, tak jak ja * Dodawanie z Meirion Hughes *, więc jest jasne, co jest od autora 1 i co jest od autora 2 – xanatos