Consuming Extension Methods in IronPython
[Update on 2008-01-06]
I’ve modified the post to reflect the information Dino provided. It turns out the SpecialName attribute is not required on extension methods but only when you are defining normal extension methods only when defining new operators.
One of my projects implemented in C# makes frequent use of extension methods. Recently I started using IronPython to script that project and what I learned is that consuming those extension methods in C# is straightforward but with IronPython some extra work is involved.
Because it isn’t entirely obvious what one needs to do to make this work, I’ll use this post to elaborate what I learned.
The diagram below summarizes it …
THE GOOD NEWS
- It’s possible
- You do NOT have to alter class A (“the extendee”)
THE BAD NEWS
- manual and repetitive work is involved
STEP-BY-STEP INSTRUCTIONS TO MODIFY THE EXTENDING CLASS
1 - Add a reference to Microsoft.Scripting.Dll
In the Visual Studio project file for B, Add a reference to the Microsoft.Scripting.Dll file (this is found in the IronPython 2.0 install folder)
2 – Add an attribute that identifies that B extends A
Add this at the top of B’s source file, just after the using statements.
A REAL BEFORE vs. AFTER COMPARISON
In this example
- we will add a Foo() method to a class we defined as well as to the string class (a.k.a System.String)
- Both Foo() methods just return a string called Bar
- The Dll that contains the extensionmethods is called “DemoIronPythonExtensionMethods.dll”
- The role of B will be played by DemoExtensionClass
- The role of A will be played by two classes – System.String and ClassToBeExtended
SOURCE CODE BEFORE | SOURCE CODE AFTER |
| using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace DemoIronPythonExtensionMethods { public class ClassToBeExtended { } public static class DemoExtensionClass { public static string Foo(this string s) { return "Bar"; } public static string Foo(this ClassToBeExtended c) { return "Bar"; } } } | using System; using System.Collections.Generic; using System.Linq; using System.Text; [assembly: Microsoft.Scripting.Runtime.ExtensionType( typeof(DemoIronPythonExtensionMethods.ClassToBeExtended), typeof(DemoIronPythonExtensionMethods.DemoExtensionClass) )] [assembly: Microsoft.Scripting.Runtime.ExtensionType( typeof(string), typeof(DemoIronPythonExtensionMethods.DemoExtensionClass) )] namespace DemoIronPythonExtensionMethods { public class ClassToBeExtended { } public static class DemoExtensionClass { public static string Foo(this string s) { return "Bar"; } public static string Foo(this ClassToBeExtended c) { return "Bar"; } } } |
| | |
WHAT HAPPENS IN IRONPYTHON 2 BEFORE | WHAT HAPPENS IN IRONPYTHON 2 AFTER |
| >>> import clr >>> import System >>> clr.AddReference("DemoIronPythonExtensionMethods.dll") >>> import DemoIronPythonExtensionMethods >>> c1 = DemoIronPythonExtensionMethods.ClassToBeExtended() >>> c1.Foo() Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: 'ClassToBeExtended' object has no attribute 'Foo' >>> s1= "HELLO WORLD" >>> s1.Foo() Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: 'str' object has no attribute 'Foo'
| >>> import clr >>> import System >>> clr.AddReference("DemoIronPythonExtensionMethods.dll") >>> import DemoIronPythonExtensionMethods >>> c1 = DemoIronPythonExtensionMethods.ClassToBeExtended() >>> c1.Foo() 'Bar' >>> s1= "HELLO WORLD" >>> s1.Foo() 'Bar'
|
PARTING THOUGHTS
- I it is a bit of a chore to annotating once per extending class.
- I hope this becomes easier in future IronPython releases
- I wonder if one could use an Aspect-Oriented Programming Tool like PostSharp to automate the task.