Делегирование динамического разрешения объекта другим экземплярам - PullRequest
2 голосов
/ 21 января 2011

В настоящее время я использую IronPython в приложении .NET 2.0.

Я хочу создать класс (в C #), экземпляры которого могут быть «расширены» экземплярами плагина. Это означает, что каждый доступ к члену, который неразрешим в моем экземпляре, должен быть перенаправлен соответствующему экземпляру плагина, предоставляющему этого участника. Тогда мой объект будет содержать приватный контейнер с этими экземплярами плагина.

AFAICS, путь к этому есть через наследование от DynamicObject. Первый шаг был легким настолько, что TryGetMember вызывается всякий раз, когда код python использует «неизвестные» члены моего экземпляра. Я также мог возвращать объекты и делегаты, которые можно использовать из кода Python.

Но, так или иначе, я застрял при попытке использовать DLR для выполнения «подпоиска» на экземпляре плагина и e. G. возвращает метод или свойство экземпляра плагина так, как это ожидает IronPython.

Любые советы приветствуются!

Спасибо!

Редактировать: Мой первоначальный вопрос не был сформулирован достаточно ясно, извините. Вот несколько моментов:

  • Решение должно работать с простым .NET 2.0, без .NET 3.5 или 4.0 не допускается.
  • Список плагинов для каждого экземпляра (это означает, что каждый экземпляр может иметь различный - но неизменный - список объектов плагинов).
  • Объекты плагина должны быть простыми объектами C # со всеми открытыми членами (или, по крайней мере, методами и свойствами).
  • Обнаружение столкновения не важно.

Еще раз спасибо.

1 Ответ

3 голосов
/ 22 января 2011

Похоже, вы хотите, чтобы ваши экземпляры плагинов были типизированы для объекта или динамического типа (вместо того, чтобы они были набраны для какого-то интерфейса, где вы эффективно проходите через запрос TryGetMember), а затем выполняете динамическое связывание с другим объектом.К счастью для вас, протокол взаимодействия DLR учитывает именно этот сценарий!Потребуется перейти на уровень IDynamicMetaObjectProvider вместо использования DynamicObject, но на самом деле это довольно просто.Я покажу вам простой пример использования InvokeMember, который работает сквозным с C # 4.0.Вам нужно будет пойти и выполнить остальные операции - в частности IronPython будет использовать GetMember вместо InvokeMember.Но это прямой процесс.

Сначала объяснение того, как вы это делаете.На уровне IDMOP DLR имеет дело с метаобъектами, а операции метаобъектов запрашивают языки, и эти метаобъекты возвращают больше метаобъектов.Вы также можете попросить язык выполнить привязку по умолчанию, и, самое главное, вы можете предложить ему, что делать, если что-то пойдет не так.

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

Итак, без дальнейших задержек, вот оно:

using System;
using System.Dynamic;
using System.Linq.Expressions;

namespace ConsoleApplication10 {
    class Program {
        static void Main(string[] args) {
            dynamic dynamicObj = new MyDynamicObject(new TestPlugin());
            dynamicObj.Foo();
            dynamicObj.Bar();
            Console.ReadLine();
        }

    }

    public class TestPlugin {
        public void Foo() {
            Console.WriteLine("TestPlugin Foo");
        }

        public void Bar() {
            Console.WriteLine("TestPlugin Bar");
        }
    }

    class MyDynamicObject : IDynamicMetaObjectProvider {
        internal readonly object[] _plugins;

        public void Foo() {
            Console.WriteLine("MyDynamicObject Foo");
        }

        public void Bar() {
            Console.WriteLine("MyDynamicObject Bar");
        }

        public MyDynamicObject(params object[] plugins) {
            _plugins = plugins;
        }

        class Meta : DynamicMetaObject {
            public Meta(Expression parameter, BindingRestrictions restrictions, MyDynamicObject self)
                : base(parameter, restrictions, self) {
            }

            public override DynamicMetaObject BindInvokeMember(InvokeMemberBinder binder, DynamicMetaObject[] args) {                
                // get the default binding the language would return if we weren't involved
                // This will either access a property on MyDynamicObject or it will report
                // an error in a language appropriate manner.
                DynamicMetaObject errorSuggestion = binder.FallbackInvokeMember(this, args);

                // run through the plugins and replace our current rule.  Running through
                // the list forward means the last plugin has the highest precedence because
                // it may throw away the previous rules if it succeeds.
                for (int i = 0; i < Value._plugins.Length; i++) {
                    var pluginDo = DynamicMetaObject.Create(Value._plugins[i],
                        Expression.Call(
                            typeof(MyDynamicObjectOps).GetMethod("GetPlugin"),
                            Expression,
                            Expression.Constant(i)
                        )
                    );

                    errorSuggestion = binder.FallbackInvokeMember(pluginDo, args, errorSuggestion);                    
                }

                // Do we want DynamicMetaObject to have precedence?  If so then we can do
                // one more bind passing what we've produced so far as the rule.  Or if the
                // plugins have precedence we could just return the value.  We'll do that
                // here based upon the member name.

                if (binder.Name == "Foo") {
                    return binder.FallbackInvokeMember(this, args, errorSuggestion);
                }

                return errorSuggestion;
            }

            public new MyDynamicObject Value {
                get {
                    return (MyDynamicObject)base.Value;
                }
            }
        }



        #region IDynamicMetaObjectProvider Members

        public DynamicMetaObject GetMetaObject(System.Linq.Expressions.Expression parameter) {
            return new Meta(parameter, BindingRestrictions.Empty, this);
        }

        #endregion
    }

    public static class MyDynamicObjectOps {
        public static object GetPlugin(object myDo, int index) {
            return ((MyDynamicObject)myDo)._plugins[index];
        }
    }
}

Выполнение этой печати:

MyDynamicObject Foo TestPlugin Bar

Показано, что для членов Foo мы предпочитаем привязку к реальному объекту, а для членов Bar мы предпочитаем Bar.Если вы добавите доступ к третьему члену Baz, он создаст исключение связующего компонента среды выполнения C #.Если бы это вызывалось из IronPython, мы бы создали AttributeError для программ на Python (MissingMemberException в .NET), и реализация JavaScript должна возвращать их программам undefined.

Таким образом, вы получаете не только свою расширяемую систему плагинов, но и вылегко получить правильное поведение в любом языке, который потребляет ваш объект.

...