2012-02-23 16 views
11

Wiem, że to, co robię, można zrobić w inny sposób, ale jestem ciekawy, jak to działa. Poniżej znajduje się uproszczony kod, który się nie kompiluje, ale powinien pokazać mój cel.Przekazywanie ogólnej funkcji jako parametru

private void Execute() 
{ 
    GeneralizedFunction("1", "2", i => Transform(i)); 
} 

void GeneralizedFunction(string aStringA, string aStringB, Func<string, T> aAction) 
{ 
    A result1 = aAction(aStringA); 
    B result2 = aAction(aStringB); 
    // Do something with A and B here 
} 

T Transform<T>(string aString) 
{ 
    return default(T); 
} 

Transform to ogólna Konwersja z ciąg do jakiegoś przedmiotu (myślę deserializacji). GeneralizedFunction wykorzystuje dwie specjalizacje transformacji: jedną dla typu A i jedną dla typu B. Wiem, że mogę to zrobić na wiele innych sposobów (np. Wprowadzając parametr dla typu obiektu), ale szukam wyjaśnienia, czy jest to możliwe lub niemożliwe w przypadku generycznych/lambdas. Jeśli Transform jest wyspecjalizowany, zanim zostanie przekazany jako parametr do GeneralizedFunction, wówczas jest to niemożliwe. Następnie pojawia się pytanie, dlaczego ta możliwość jest ograniczona.

+0

co chodzi chcesz zrobić dokładnie? ponieważ nie chcesz podawać żadnych informacji dotyczących funkcji Transformalized, dlaczego nie zaakceptować func ponownie, biorąc ciąg i zwracając obiekt (z których każdy wie, że wszyscy są *) – Polity

+0

Chodzi o to, że "Zrób coś z Symbol zastępczy A i B ukrywa część problematyczną. Czy A i B zawsze będą szczególnymi rodzajami? Wtedy nie potrzebujesz generycznych. Czy są to rodzaje arbitralne (być może z ograniczeniami)? Następnie "GeneralizedFunction" musi być w nich ogólna. – AakashM

+0

A i B są konkretnymi typami, ale transformacja jest funkcją ogólną. – Max

Odpowiedz

1

Spróbuj następujący podpis:

void GeneralizedFunction<T>(string aStringA, string aStringB, Func<string, T> aAction) 

(Zauważ, że GeneralizedFunction musi być rodzajowy; kompilator automatycznie odgadnąć typ parametru podczas wywoływania metody).

+0

Spróbowałem. Problem polega na tym, że próbujesz odnieść się do obu typów A i B z T tutaj. Miałem zamiar usunąć z deklaracji funkcji. – Max

+0

Wtedy będziesz musiał zastąpić A i B przez T. – Matthias

+0

Potrzebuję wykonać określone operacje na obiektach wewnątrz GeneralizedFunction dla uproszczenia. Nie zapisałem żadnych szczegółów w moim przykładowym kodzie, ale wszystko, co jest napisane, jest konieczne. – Max

0
void GeneralizedFunction<T>(string aStringA, string aStringB, Func<string, T> aAction) 
{ 
    A result1 = aAction(aStringA); 
    B result2 = aAction(aStringB); 
} 

T Transform<T>(string aString) 
{ 
    return default(T); 
} 
4

To, o co prosisz, nie jest możliwe przy użyciu samych leków generycznych. Kompilator musi wygenerować dwie wersje maszynowej wersji Transform: jedną, aby zwrócić typ A i jedną dla typu B. Kompilator nie ma możliwości wygenerowania tego podczas kompilacji; tylko przez uruchomienie kodu będzie wiedział, że A i B są wymagane.

Jednym ze sposobów rozwiązania tego byłoby przekazać w dwóch wersjach:

private void Execute() 
{ 
    GeneralizedFunction("1", "2", i => Transform<A>(i), i => Transform<B>(i)); 
} 

void GeneralizedFunction(string aStringA, string aStringB, Func<string, A> aAction, Func<string, B> bAction) 
{ 
    A result1 = aAction(aStringA); 
    B result2 = bAction(aStringB); 
} 

Kompilator wie dokładnie to, czego potrzebuje, aby wygenerować w tym przypadku.

+0

Tak, wiem, że mogłem przekazać dwa wystąpienia, ale miałem nadzieję, że możliwe jest przekazanie funkcji GENERIC (stąd tytuł mojego pytania) i stworzenie dwóch specjalizacji o tej ogólnej funkcji wewnątrz funkcji GeneralizedFunction. – Max

+0

Wiem, że kod nie jest tym, czego potrzebujesz. Twoje pytanie brzmiało, dlaczego ta możliwość jest ograniczona. Mam nadzieję, że teraz rozumiecie, dlaczego to, co chcecie, nie będzie działać z kompilatorem. –

+0

Jeśli spojrzę na kod IL wygenerowany dla funkcji Transform, wydaje się, że jest tylko jedna jej wersja. Nawet gdy zastosuję go do dwóch klas obiektów. Wydaje się więc, że specjalizacja funkcji ogólnej jest wykonywana w czasie wykonywania. Czyż nie? – Max

1

Wygląda na to, że odpowiedź brzmi "nie".

Po wywołaniu Transform bezpośrednio, trzeba określić parametr typu:

int i = Transform<int>(""); 

Tak hipotetycznie, jeśli można przekazać niekompletnie-skonstruowaną funkcję rodzajowy jak chcesz, to trzeba określić również parametry:

void GeneralizedFunction(string aStringA, string aStringB, Func<string, T> aAction) 
{ 
    A result1 = aAction<A>(aStringA); 
    B result2 = aAction<B>(aStringB); 
    // Do something with A and B here 
} 

Wydaje mi się, że można to hipotetycznie zrobić, jeśli C# ma taką składnię.

Ale jaki jest przypadek użycia? Oprócz przekształcenia łańcuchów do wartości domyślnej dowolnego typu, nie widzę zbyt dużego wykorzystania tego. W jaki sposób można zdefiniować funkcję, która dałaby sensowny wynik w jednym z dwóch różnych typów przy użyciu tej samej serii instrukcji?

EDIT

Analiza dlaczego nie jest to możliwe:

Kiedy użyć wyrażenia lambda w kodzie, jest kompilowany do obu delegata lub drzewa ekspresji; w tym przypadku jest to delegat. Nie możesz mieć instancji typu "otwartego"; innymi słowy, aby utworzyć obiekt z rodzaju ogólnego, należy określić wszystkie parametry typu.Innymi słowy, nie ma sposobu, aby mieć instancję delegata bez dostarczania argumentów dla wszystkich jego parametrów typu.

Jedną z przydatnych funkcji kompilatora C# są niejawne konwersje grup metod, w których nazwa metody ("grupa metod") może zostać niejawnie przekształcona w typ delegata reprezentujący jedno z przeciążeń tej metody. Podobnie, kompilator niejawnie konwertuje wyrażenie lambda na typ delegata. W obu przypadkach kompilator wysyła kod, aby utworzyć instancję typu delegata (w tym przypadku, aby przekazać ją do funkcji). Ale instancja tego typu delegata nadal musi mieć argument typu dla każdego z jego parametrów typu.

Aby przejść funkcję ogólną jako ogólną funkcję Wydaje się, że kompilator musi być w stanie przejść grupę metody lub Wyrażenie lambda sposobu bez konwersji, tak aAction parametr miałby jakoś rodzaj "grupy metod" lub "wyrażenia lambda". Następnie niejawna konwersja do typu delegata może się zdarzyć w witrynach połączeń A result1 = aAction<A>(aStringA); i B result2 = aAction<B>(aStringB);. Oczywiście, w tym momencie jesteśmy dobrze we wszechświecie kontrowersyjnych i hipotetycznych.

Rozwiązanie wpadłem na obiad był to, zakładając funkcję Deserialize<T> który pobiera ciąg zawierający dane odcinkach i zwraca obiekt typu T:

void GeneralizedFunction<T>(string aStringA, string aStringB, Func<T, string> stringGetter) 
{ 
    A result1 = Deserialize<A>(stringGetter(aStringA)); 
    B result2 = Deserialize<B>(stringGetter(aStringB)); 
} 

void Example(string serializedA, string serializedB, string pathToA, string pathToB, FileInfo a, FileInfo b) 
{ 
    GeneralizedFunction(serializedA, serializedB, s => s); 
    GeneralizedFunction(pathToA, pathToB, File.ReadAllText); 
    GeneralizedFunction(a, b, fi => File.ReadAllText(fi.FullName)); 
} 
+0

Jednym z przypadków użycia jest deserializacja. Łańcuch jest reprezentacją obiektu, a Transform tworzy instancję tego obiektu z jego reprezentacji łańcuchowej. – Max

+0

@Max Ale jaki jest pożytek z przekazania 'Transform' do' GeneralizedFunction' zamiast zwykłego wywoływania go bezpośrednio? W każdym razie, czy ogólna 'T Transform (string)' po prostu będzie wygodną metodą wokół 'obiektu Deserialize (type, string)' jak 'T Transform (string s) {return (T) Deserialize (typeof (T) , s); } ' – phoog

+0

Na przykład Transform1 może przekonwertować ciąg na obiekt, Transform2 może przekonwertować plik, który ciąg wskazuje na obiekt – Max

4

Ta odpowiedź nie wyjaśnia przyczynę dlaczego, właśnie jak obejść ograniczenia.

Zamiast przechodzącą rzeczywistej funkcji można przekazać obiekt, który ma taką funkcję:

interface IGenericFunc 
{ 
    TResult Call<TArg,TResult>(TArg arg); 
} 

// ... in some class: 

void Test(IGenericFunc genericFunc) 
{ 
    // for example's sake only: 
    int x = genericFunc.Call<String, int>("string"); 
    object y = genericFunc.Call<double, object>(2.3); 
} 

dla konkretnego przypadku użycia, można uprościć do:

interface IDeserializerFunc 
{ 
    T Call<T>(string arg); 
} 

// ... in some class: 
void Test(IDeserializerFunc deserializer) 
{ 
    int x = deserializer.Call<int>("3"); 
    double y = deserializer.Call<double>("3.2"); 
} 
Powiązane problemy