Использование методов расширения через переопределение TryInvokeMember для объекта DynamicObject - PullRequest
2 голосов
/ 09 мая 2011

Я чувствую, что мне нужно дать небольшую справочную информацию о том, почему я делаю то, что я делаю, потому что я хочу открыть это для предложений и критики, и дать надежду программистам XNA, использующим Blender, которые могли бы прочитать это,Учитывая длину этого поста, пожалуйста, не стесняйтесь переходить к последнему абзацу, если вам не нужны подробности моей проблемы.

Я работаю над проектом расширения XNA Content Pipeline, которыйчитает файлы .blend (созданные Blender ) и преобразует их в данные, загружаемые из игры XNA, чтобы избежать необходимости экспортировать новую модель .FBX или .OBJ из Blender каждый раз, когда я делаю какие-либо небольшие изменения, кака также (надеюсь) сделать некоторую XNA-совместимую поддержку удивительных возможностей Блендера в области частиц и физики.

Не вдаваясь слишком глубоко во внутреннюю работу Блендера , я хотел бы описать свое понимание того, как.blend файлы работают.Пожалуйста, поправьте меня, если вы более осведомлены в этом вопросе.

Blender сохраняет файлы в «блоках» байтов.Большинство из этих блоков содержат данные, представляющие объекты и настройки в трехмерной сцене, а последний блок в файле (называемый блоком SDNA) содержит то, что можно рассматривать как очень простые структуры в стиле C, каждая из которых имеет уникальный идентификатор, а также несколько полей различных типов.Поля этих структур могут быть простых типов, таких как int или float, или они могут относиться к типам, определенным в блоке SDNA.

Например, вот представление полупсевдокодаID структуры SDNA:

structure IDPropertyData
{
    void *pointer;
    ListBase group;
    int val;
    int val2;
}

Как видите, поля *pointer, val и val2 могут быть представлены во время выполнения простыми значениями типа void* илиint.Поле group, однако, имеет тип ListBase, который определен в другом месте в блоке SDNA файла.

Именно здесь начинают играть новые динамические функции C #: я создал класс (называемыйBlenderObject) что, учитывая структуру SDNA (это «тип SDNA») и кусок байтов, ведет себя как экземпляр этой структуры, сохраняя либо значение простого типа для себя, либо коллекцию других BlenderObjectэкземпляры, каждый из которых представляет одно из своих «полей».Это позволяет пользователю моей библиотеки писать код, подобный следующему:

//Get a BlendContent instance that contains the file's information.
BlendContent content = BlendContent.Read(filePath);
//Get the 0th (and only) block containing data for the scene (code "SC")
BlendFileBlock sceneBlock = content.FileBlocks["SC", 0];
//Get the BlenderObject that represents the scene
dynamic scene = sceneBlock.Object;
//Get the scene's "r" field, whose SDNA type is RenderData.
dynamic renderData = scene.r;
//Get the x and y resolution of the rendered scene
float
    xParts = renderData.xparts,
    yParts = renderData.yparts;

scene и renderData являются «сложными» BlenderObject экземплярами (каждый из которых имеет коллекцию полей, а непрямое значение), а также xparts и yparts являются "простыми" BlenderObject экземплярами (каждый из которых имеет для себя прямое значение простого типа, а не набор полей).Каждый BlenderObject ведет себя точно так, как вы ожидаете, если его тип SDNA был конкретно скомпилированным типом в сборке, и моя цель - использовать динамику для представления объектов Blender.

Чтобы упростить использование моей библиотекиЯ работаю над перегрузкой DynamicObject методов таким образом, чтобы "простые" BlenderObject экземпляры вели себя как их прямые значения.Например, пусть foo будет BlenderObject с прямым значением int -типа, скажем, 4. Я хочу иметь возможность сделать следующее с foo:

string s = foo.ToString();
Console.WriteLine(s);

Цель первой строки - вызвать метод ToString внутреннего значения foo, а не самого foo, поэтому переопределение foo TryInvokeMember использует отражение для вызова Int32.ToString() своего значения4, а не BlenderObject.ToString() на себя.Этот рефлексивный вызов метода прекрасно работает (как и та же концепция, применяемая к индексаторам для прямых значений типа массива), за исключением случаев, когда я пытаюсь что-то вроде следующего:

string s = foo.Bar();
Console.WriteLine(s);

Bar - метод расширенияопределено в моей сборке и поэтому отражает значение foo, равное 4, очевидно, не может его найти, и, конечно, создаются исключения.Мой вопрос, наконец, таков:

Как мне найти и / или кэшировать методы расширения, которые можно применить к объекту?Я знаю , как найти методы расширения с отражением , но делать это каждый раз, когда метод расширения вызывается для динамического BlenderObject экземпляра, будет действительно медленно.Есть ли более быстрый способ найти методы расширения, отличные от описанных в этом вопросе?Если нет, то как мне пройти интернирование методов расширения, чтобы мне нужно было найти их только один раз и получить быстрый доступ к ним снова?Извините за долголетие, и заранее спасибо за любые полезные ответы / комментарии.

РЕДАКТИРОВАТЬ:

Как ответил @spender, простой способ решить мою проблему - воспользоваться словарем (хотя я использую Dictionary<Type, Dictionary<CallInfo, MethodInfo>>, чтобы легко использоватьCallInfo предоставляется InvokeMemberBinder, переданным DynamicObject.TryInvokeMember).Однако моя реализация привела меня к другому вопросу:

Как я могу получить типы, определенные в сборках, на которые ссылается вызывающая сборка?Например, рассмотрим этот код: object Foo (dynamic blenderObject) {return blenderObject.x.Baz ();} Если этот код находится в проекте, который ссылается на мою библиотеку Blender, и метод расширения Baz() определен в другой сборке, на которую ссылается этот проект, но не в моем конвейере Blender, как бы я мог найти Baz() в моем Blender?трубопровод?Это вообще возможно?

1 Ответ

1 голос
/ 09 мая 2011

Я бы просто кэшировал обнаруженные методы заранее Dictionary<Type,ICollection<MethodInfo>>.Поиск будет быстрым.

...