Skip to content

Instantly share code, notes, and snippets.

@atifaziz
Created May 1, 2010 11:38
Show Gist options
  • Save atifaziz/386261 to your computer and use it in GitHub Desktop.
Save atifaziz/386261 to your computer and use it in GitHub Desktop.
// This example is adapted from the sample supplied with DynamicObject
// documentation on MSDN:
//
// http://msdn.microsoft.com/en-us/library/system.dynamic.dynamicobject.aspx
//
// The example has been adapted to be case-sensitive by default and
// test the dynamic object implementation across C#, IronPython 2.6.1 and
// IronRuby 1.0.
//
// The configuration needed to run this example is supplied at the bottom.
//
// This example SHOULD have the following output:
// C#: Ellen Adams
// py: Ellen Adams
// rb: Ellen Adams
//
// Instead, it produces an exception when IronRuby is involved:
// C#: Ellen Adams
// py: Ellen Adams
//
// Unhandled Exception: System.MissingMethodException: undefined method `firstname' for DynamicObject:DynamicObject
// at Microsoft.Scripting.Interpreter.ThrowInstruction.Run(InterpretedFrame frame)
// at Microsoft.Scripting.Interpreter.Interpreter.Run(InterpretedFrame frame)
// at Microsoft.Scripting.Interpreter.LightLambda.Run2[T0,T1,TRet](T0 arg0, T1 arg1)
// at IronRuby.Runtime.RubyScriptCode.Run(Scope scope, Boolean bindGlobals)
// at IronRuby.Runtime.RubyScriptCode.Run(Scope scope)
// at Microsoft.Scripting.SourceUnit.Execute(Scope scope, ErrorSink errorSink)
// at Microsoft.Scripting.Hosting.ScriptSource.Execute(ScriptScope scope)
// at Program.TestScript(ScriptEngine engine, Object person, String script)
// at Program.Main()
//
// To make it work in IronRuby, re-run the compiled example with a non-zero
// argument to initialize DynamicObject with a dictionary using a
// case-insensitive string comparer for keys. This example is thus meant to
// demonstrate that the default implementation of
// System.Dynamic.DynamicObject does not work out of the box (like the
// System.Dynamic.ExpandoObject subclass) with IronRuby unless TryGetMember
// internally handles the member in a case-insensitive manner, ignoring any
// binder hints. The MissingMethodException is due to the way
// DynamicObject's meta object implementation combines the fallback tree
// with its own. IronRuby calls BindInvokeMember for `person.firstname` but
// supplies a fallback that attempts a second binding with the
// member name changed to `Firstname`. This fallback gets wired instead so
// binding is attempted against `Firstname` but never `firstname`.
//
using System;
using System.Collections.Generic;
using System.Dynamic;
using System.Linq;
using Microsoft.Scripting.Hosting;
class DynamicObject : System.Dynamic.DynamicObject
{
// The inner dictionary.
readonly IDictionary<string, object> dictionary;
public DynamicObject(IDictionary<string, object> dictionary)
{
this.dictionary = dictionary;
}
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
return dictionary.TryGetValue(binder.Name, out result);
}
public override bool TrySetMember(SetMemberBinder binder, object value)
{
dictionary[binder.Name] = value;
return true;
}
}
class Program
{
static void Main(string[] args)
{
var ignoreCase = args.Any() && Convert.ToInt32(args[0]) != 0;
var comparer = ignoreCase ? StringComparer.OrdinalIgnoreCase : null;
var dictionary = new Dictionary<string, object>(comparer);
dynamic person = new DynamicObject(dictionary);
person.firstname = "Ellen";
person.lastname = "Adams";
Console.WriteLine("C#: " + person.firstname + " " + person.lastname);
var runtime = ScriptRuntime.CreateFromConfiguration();
var py = runtime.GetEngine("py");
TestScript(py, (object) person,
@"print 'py:', person.firstname, person.lastname");
var rb = runtime.GetEngine("rb");
TestScript(rb, (object) person,
@"puts 'rb: ' + person.firstname + ' ' + person.lastname");
}
private static void TestScript(ScriptEngine engine, object person, string script)
{
var scope = engine.CreateScope();
scope.SetVariable("person", person);
engine.CreateScriptSourceFromString(script).Execute(scope);
}
}
//<!--
// The configuration needed to run this example is supplied below. Uncomment
// and then move it into an app.config. The paths in the href attributes of
// the <codeBase> elements may need to be changed depending on where
// IronPython 2.6.1 and IronRuby 1.0 (where both should be the distributions
// built against Microsoft .NET Framework 4.0) are installed on your machine.
//-->
//<configuration>
// <configSections>
// <section name="microsoft.scripting"
// type="Microsoft.Scripting.Hosting.Configuration.Section, Microsoft.Scripting, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
// requirePermission="false" />
// </configSections>
// <microsoft.scripting>
// <languages>
// <language names="IronPython;Python;py"
// extensions=".py" displayName="IronPython 2.6.1"
// type="IronPython.Runtime.PythonContext, IronPython, Version=2.6.10920.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
// <language names="IronRuby;Ruby;rb"
// extensions=".rb" displayName="IronRuby 1.0"
// type="IronRuby.Runtime.RubyContext, IronRuby, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
// </languages>
// </microsoft.scripting>
// <runtime>
// <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
// <dependentAssembly>
// <assemblyIdentity name="IronPython" publicKeyToken="31bf3856ad364e35" />
// <codeBase version="2.6.10920.0"
// href="file:///C:/Program Files/IronPython/2.6.1/IronPython.DLL"/>
// </dependentAssembly>
// <dependentAssembly>
// <assemblyIdentity name="IronRuby" publicKeyToken="31bf3856ad364e35" />
// <codeBase version="1.0.0.0"
// href="file:///C:/Program Files/IronRuby 1.0v4/bin/IronRuby.DLL"/>
// </dependentAssembly>
// <dependentAssembly>
// <assemblyIdentity name="IronRuby.Libraries" publicKeyToken="31bf3856ad364e35" />
// <codeBase version="1.0.0.0"
// href="file:///C:/Program Files/IronRuby 1.0v4/bin/IronRuby.Libraries.DLL"/>
// </dependentAssembly>
// </assemblyBinding>
// </runtime>
//</configuration>
@jschementi
Copy link

Problem here is person.firstname does not invoke TryGetMember, it invokes TryInvokeMember. Ruby's method access and method invocation is the same step (because parens are optional to invoke), where as in Python and C# you get the member with person.firstname, and if it's a method you can then invoke it with person.firstname().

http://ironruby.codeplex.com/workitem/4752

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment