Czas rozprawić się z wykonywaniem wyrażeń reprezentujących wywołania funkcji, a zarazem zamknąć minicykl postów o wykonywaniu wyrażeń (poprzednie części: wartości stałe, zbiory wyrażeń). Tym samym rdzeń DSLExecutora zostanie ukończony i będę mógł przejść do implementacji funkcjonalności będących bliżej użytkownika. Ale skupmy się na wywoływaniu funkcji. Pracy jest niemało, dlatego opis zostanie rozbity na dwa posty. Do dzieła!
Kroki wykonania FunctionExpression
Na początek, Czytelniku, zajrzyj do opisów założeń i implementacji DSLExecutora w obszarze funkcji:
- Funkcje i ich handlery definiowane są tak.
- Model wyrażenia reprezentującego wywołanie funkcji wygląda tak.
- Poglądowy schemat wykonania FunctionExpression pokazany jest tutaj.
A teraz, w kontekście powyższego, zadajmy sobie pytanie: jak dokładnie wygląda wykonanie funkcji? Od razu odpowiem:
- Utworzenie obiektu funkcji.
- Wypełnienie właściwości tego obiektu wartościami uzyskanymi poprzez wykonanie wyrażeń będących argumentami funkcji.
- Uzyskanie instancji FunctionHandlera odpowiadającego funkcji.
- Przekazanie obiektu funkcji FunctionHandlerowi i zwrócenie zwróconej przez niego wartości.
Sygnatura metody wykonującej funkcję wygląda więc tak:
TResult ExecuteFunction<TFunction, TResult> (IDictionary<string, IExpression> argumentExpressions) where TFunction : IFunction<TResult>
Ale jak wywołać tę generyczną metodę na podstawie instancji niegenerycznego IFunctionExpression? (Przypomnę, że nadrzędna klasa wykonująca wyrażenia – ExpressionExecutor – nie jest generyczna.)
Wydłubanie typu funkcji z IFunctionExpression
Aby to zrobić, najpierw muszę poznać typ funkcji (TFunction) i typ jej wartości zwracanej (TResult). Poznanie typu funkcji jest proste – wystarczy odwołać się do właściwości IFunctionExpression.FunctionType. A na jego podstawie, jako że implementuje on interfejs IFunction<TResult>, mogę dostać się do typu wartości zwracanej. Problem sprowadza się do wydłubania argumentu generycznego interfejsu na podstawie typu implementującego ten interfejs. Aby to zrobić, wprowadziłem następującą metodę:
static Type GetGenericInterfaceDefinitionImplementation (this Type type, Type definition) { if (type.IsInterface && type.IsConstructedGenericType && type.GetGenericTypeDefinition() == definition) { return type; } foreach (var i in type.GetInterfaces()) { if (i.IsConstructedGenericType && i.GetGenericTypeDefinition() == definition) { return @interface; } } return null; }
Zwraca ona “skonstruowany” typ generyczny, tzn. typ z wypełnionymi argumentami. Przykładowo, dla poniższej klasy:
class CurrentDirFunction : IFunction<string> { }
Zawołanie:
var impl = typeof(CurrentDirFunction) .GetGenericInterfaceDefinitionImplementation( typeof(IFunction<>));
Zwróci typ IFunction<string>. Aby dostać typ wartości zwracanej, wystarczy więc zawołać:
impl.GetGenericArguments()[0]
Bardziej rozbudowaną implementację takiego wydłubywania (obsługującą nie tylko interfejsy, ale i klasy) wrzuciłem tutaj.
Kończąc wątek, wykonanie ExecuteFunction na podstawie IFunctionExpression wygląda tak:
public object Execute(IFunctionExpression expression) { var tFunc = expression.FunctionType; var impl = tFunc .GetGenericInterfaceDefinitionImplementation( typeof(IFunction<>)); var tResult = impl.GetGenericArguments()[0]; return _executeFunctionMethod .MakeGenericMethod(tFunc, tResult) .Invoke(this, new object[] { expression.ArgumentExpressions }); }
(_executeFunctionMethod to pole typu MethodInfo przechowujące metodę ExecuteFunction pokazaną wyżej.)
Tyle na dzisiaj. W kolejnej części opiszę wykonywanie samych funkcji, czyli implementację metody ExecuteFunction – zapraszam!