Да, у D есть отражение, но нет, оно не похоже на Java.
Отражение D приходит в виде строительных блоков времени компиляции, а не методов времени выполнения.Конечно, вы можете создавать методы времени выполнения самостоятельно, но это не будет работать из коробки со всем.
Я на самом деле только что написал сегодня, что отражение переходит на метод, чтобы показать его свойства и позволитьВы редактируете это: https://twitter.com/adamdruppe/status/1066390612516179968 Это еще не сделано, но я сделаю ссылку, чтобы вы могли увидеть некоторые из них в действии: https://github.com/adamdruppe/arsd/commit/5411dd9844869909466f2091391abe091b529bf8#diff-7a88942576365ca3d7f389b766344477R6587
В любом случае, я создаю методыиз простых циклов, используя информацию об отражении.Язык предоставляет два средства: __traits и выражение is для этого:
https://dlang.org/spec/traits.html
https://dlang.org/spec/expression.html#IsExpression
И стандартная библиотека переносится и расширяется с помощью stdМодуль .traits
http://dpldocs.info/experimental-docs/std.traits.html
(или, если вы предпочитаете официальный сайт, в основном, с теми же документами, просто сложнее читать / перемещаться: https://dlang.org/phobos/std_traits.html)
Вы можете комбинировать это с другими методами генерации кода, такими как шаблоны, и такими традиционными вещами, как интерфейсы и конструкторы, для создания среды выполнения.
Но для простого случая попробуйте что-то вроде этого:
import std.stdio;
import std.conv;
import std.traits;
class MyClass {
void myMethod() {}
int anotherMethod(int a) { return a; }
// this is the runtime bridge function. The trick here is to do
// a switch, just like your example, but the innards are auto-generated
// from the compile time reflection.
string call(string methodName, string[] args) {
// it starts as a plain switch...
method_switch: switch(methodName) {
// but inside, we use a static foreach - a compile-time loop -
// over the __traits(allMembers) magic, which gives a list of all member names
static foreach(inspecting; __traits(allMembers, typeof(this))) {
case inspecting: { // you can create switch cases inside these static loops
// for this example, I only want to use callable methods, so this
// static if - a compile time if statement - will filter out anything else.
//
// It is possible to do more, like plain data members, child classes, and more,
// but that will add a lot more code. Same basic ideas with each of them though.
static if(isCallable!(__traits(getMember, this, inspecting))) {
// after we confirm it is callable, we can get a delegate of it
// (same as normally doing `&member.method`) to call later.
auto callable = &__traits(getMember, this, inspecting);
// next is building the argument list. Parameters comes from the std.traits
// module in the standard library and gives an object representing the function's
// parameters. We can loop over these and set them!
Parameters!callable arguments;
foreach(i, ref arg; arguments) { // ref loop cuz we setting the arg members..
// so for the runtime bridge here, I took everything as strings and
// want to convert them to the actual method arguments. In many cases,
// that automatic conversion will not be possible. The next bit of magic,
// __traits(compiles), will take some code and return true if it successfully
// compiles. Using the static if block, I can turn what would be compile time
// errors into a runtime exception instead.
static if(__traits(compiles, to!(typeof(arg))(args[i])))
// note that to is from the stdlib again: std.conv. It converts
// a thing from one type to another. Here, I ask it to convert our
// string (args is the runtime array of strings the user passed) to
// whatever the type is that the method expects.
//
// Note that this might throw an exception if the string is wrong.
// For example, passing "lol" to a method expecting an int will throw
// an exception saying cannot convert string "lol" to int.
arg = to!(typeof(arg))(args[i]);
else
// or if the conversion didn't compile at all, it will always throw.
throw new Exception("method " ~ methodName ~ " not callable with this reflection code because of incompatible argument type");
}
// now that we have the arguments, time to tackle the return value.
// the main special case here is a void return - that is not a value
// and thus cannot be converted. So we do it separately.
// Otherwise, though, inside we just call our callable from above with
// the arguments from above and return the value converted to string!
static if(is(ReturnType!callable == void)) {
// it returned void, so call it and return null
// because the language won't let us return void
// directly as a string nor convert it easily.
callable(arguments);
return null;
} else {
// it returned something else, just call the function
// and convert it to a string
return to!string(callable(arguments));
}
}
} break method_switch;
}
default:
throw new Exception("no such method " ~ methodName);
}
assert(0); // not reached
}
}
// and let's call them with some strings. You could also get these strings from
// the user (just btw remember to .strip them if they come from readln, otherwise
// the trailing newline character will cause a method not found exception.)
void main() {
auto obj = new MyClass();
writeln(obj.call("myMethod", []));
writeln(obj.call("anotherMethod", ["5"]));
}
Это может помочь скопировать / вставить этот код с веб-сайта и прочитать комментарии в обычном редакторе, поскольку переполнение стека часто заставляет вас прокручивать, и это сложно.Я показываю основные идеи в комментариях.
Как только вы однажды напишите эту функцию моста отражения и заставите ее работать как-то, что вам это нравится ... вы можете добавить столько методов, сколько захотите, и это будетработа!
На самом деле, вы даже можете сделать метод call
там частью интерфейса и определить часть тела mixin template
(см. https://dlang.org/spec/template-mixin.html) и вставить егов любой из ваших классов.
class MyNewClass : Reflectable {
mixin CallImplementation;
}
// it will now work there!
Интерфейс позволяет ссылаться на него из базовых классов (по большей части так же, как в Java), а шаблон mixin позволяет копировать этого поставщика отражений в каждый дочерний класс.так что он дает все методы даже на дочерних классах.Java сделает это за вас автоматически, но в D вам нужно добавить эту строку mixin для каждой.Не слишком много хлопот, но есть что рассмотреть.(На самом деле D может делать это тоже автоматически ... но для этого требуется взломать базовую библиотеку времени выполнения, поэтому это довольно сложная тема и полезная только в особых ситуациях (поскольку вы должны использовать эту взломанную библиотеку во всем проекте).возможно, вам это не пригодится, просто намекнуть, что он там есть.)
С помощью интерфейса btw вы также можете добавить статический конструктор в ваши классы, чтобы зарегистрировать их в некотором ассоциативном массиве во время выполнения или переключателе или любом другом из имен классов вфабричные функции, и создавать их из строк тоже.Нет особого специального кода, который бы осознавал это, это тот же самый тип шаблона, который вы, вероятно, видели раньше, но если вам нужны новые объекты класса из строк вместо простого редактирования существующих объектов, вот как я начну.
Я оставлю эти детали для вас, чтобы поиграть, пусть я знаю, если здесь что-то не имеет смысла.