Pergunta

I'm currently hosting IronPython in a .NET 2.0 application.

I want to create a class (in C#) whose instances can be "extended" by plugin instances. That means, each member access which is unresolvable on my instance should be forwarded to the appropriate plugin instance providing that member. My object will hold a private container with those plugin instances then.

AFAICS, the way to go there is via deriving from DynamicObject. The first step was easy so far that TryGetMember is called whenever python code uses "unknown" members of my instance. I also could return objects and delegates which could be used from the python code.

But, somehow, I got stuck when trying to use the DLR to perform the "subsearch" on the plugin instance and e. G. return a method or property of the plugin instance in the way IronPython expects it.

Any hints are welcome!

Thanks!

Edit: My original question was not formulated clear enough, sorry. Here some points:

  • The solution has to run with plain .NET 2.0, no .NET 3.5 or 4.0 allowed.
  • The plugin list are per instance (that means each instance can have a different - but immutable - list of plugin objects).
  • The plugin objects should be plain C# objects with all public members (or at least methods and properties) mapped.
  • Collision detection is not important.

Thanks again.

Foi útil?

Solução

What it sounds like you want to do is have your plugin instances typed to object or dynamic (versus having them typed to some interface where you effectively pass through the TryGetMember request) and then perform a dynamic binding against another object. Luckily for you the DLR interop protocol allows for exactly this scenario! It'll require to drop down to the IDynamicMetaObjectProvider layer instead of using DynamicObject but it's actually pretty simple. I'll show you a simple example using InvokeMember that works end-to-end w/ C# 4.0. You'll need to go and do the rest of the operations - in particular IronPython will use GetMember instead of InvokeMember. But that's a straight forward process.

First an explanation of how you do it. At the IDMOP level the DLR deals with meta objects and languages request operations from meta objects and those meta objects return more meta objects. You can also ask the language to perform it's default binding and most importantly you can provide to it a suggestion of what to do when things go wrong.

Based upon that you can ask the language to try and bind to each one of your plugins and your own object. Depending on whether you want plugins to have precedence, or your dynamic object, you can perform the binding to yourself last. This sample demonstrates both techniques based upon the member names.

So without further delay, here it is:

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];
        }
    }
}

Running this prints:

MyDynamicObject Foo TestPlugin Bar

Showing that for Foo members we prefer the binding on the actual object, and for Bar members we prefer Bar. If you add access to a third Baz member it produces C#'s runtime binder exception. If this was called from IronPython we'd produce an AttributeError to Python programs (a MissingMemberException in .NET) and a JavaScript implement should return undefined to their programs.

So you get not only your extensible plugin system you also easily get the correct behavior in any langauge which consumes your object.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top