//LispDynamicDispatchExample.cs copyright 2012 (c) Tony Tanzillo
using System;
using System.Linq;
using System.Reflection;
using System.Collections.Generic;
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.Geometry;
using Autodesk.AutoCAD.Runtime;
using Autodesk.AutoCAD.EditorInput;
namespace Lisp.Interop.Dynamic
{
public static class LispDynamicDispatchExample
{
/// <summary>
///
/// Dynamic Dispatch: http://en.wikipedia.org/wiki/Dynamic_dispatch
///
/// The ability to late-bind to one of several overloads of
/// a method or function, dynamically at runtime, based on
/// the number and type(s) of a set of given arguments.
///
/// In statically-compiled languages like C or C++, dynamic
/// dispatch is not supported. In .NET, dynamic dispatch is
/// supported, but only through the use of System.Dynamic.
///
/// In languages that don't support Dynamic Dispatch, when
/// multiple overloaded versions of a method are defined,
/// the version that will be called is determined at compile
/// time (this is referred to as 'binding'). If no method
/// matching the given arguments is found, a compile-time
/// error occurs.
///
/// With dynamic dispatch, the process of binding to methods
/// is deferred until runtime. Hence, if no matching method can
/// be found, a runtime error occurs.
///
/// As it turns out, this is an excellent example of how to
/// leverage the framework and get it to do a lot work for you,
/// that would otherwise require significant amounts of very
/// complicated and error-prone code.
///
/// The (dynamic-dispatch) LISP function can be called with
/// a variety of arguments, as defined by the seven overloads
/// of the MyLispFunction() method included below. When the
/// Dispatch() extension method is passed the arguments and
/// MethodInfo of the caller, it will try to find a matching
/// overload, and if one is found, it invokes the overload
/// with the given arguments. If the arguments do not match
/// one of the overloads, an error is raised.
///
/// LIST arguments are not supported in this demo, but the
/// ability to support lists is theoretically possible with
/// some caveats.
///
/// The 'params' modifier that allows a method to accept an
/// array of any number of arguments of a specified type is
/// not currently supported. While supporting 'params' may be
/// possible using custom binding, it would be non-trivial.
///
/// </summary>
/// The "base" method which is called by the AutoCAD
/// managed runtime when the (dynamic-dispatch) LISP
/// function is called:
[LispFunction( "dynamic-dispatch" )]
public static void MyLispFunction( ResultBuffer args )
{
args.Dispatch( MethodInfo.GetCurrentMethod() );
}
/// Below are 7 overloads of the above MyLispFunction() method.
/// One of those overloads will be called if its argument list
/// matches the set of arguments passed from LISP (in this case,
/// 'match' means a method takes the same number of arguments as
/// was passed from LISP, and that each passed argument can be
/// assigned to the corresponding method argument variable).
///
/// The overloads must be static methods, with the same name
/// as the 'base' method that has the LispFunction attribute
/// applied to it (overloads must NOT have the LispFunction
/// attribute applied to them).
///
/// Given the set of overloads implemented below, any of the
/// following calls to (dynamic-dispatch) will succeed:
///
/// (dynamic-dispatch "foo")
/// (dynamic-dispatch "foo" 10)
/// (dynamic-dispatch "foo" "bar")
/// (dynamic-dispatch pi (getvar "lastpoint"))
/// (dynamic-dispatch 2.459 (entlast) "hello")
/// (dynamic-dispatch "foo" (ssget "x"))
/// (dynamic-dispatch "foo" T)
/// (dynamic-dispatch "foo" NIL)
///
/// Each of the above calls resolves to a call to one of
/// the following overloads. Also note that this mechanism
/// provides for optional arguments in the exact same way
/// they can be implemented in .NET code, via the use of
/// method overloading. For example, the first overloaded
/// function takes a string. The second overload takes a
/// string and an int, implicitly making the second int
/// argument optional.
///
/// The functionality provided by the DynamicDispatcher
/// class achives something that would otherwise require
/// a sigificant amount of somewhat "messy", error-prone
/// code.
public static void MyLispFunction( string a )
{
Utils.WriteLine( ">>> MyLispFunction( {0} )", a );
}
public static void MyLispFunction( string a, int b )
{
Utils.WriteLine( ">>> MyLispFunction( {0}, {1} )", a, b );
}
public static void MyLispFunction( double a, Point3d b )
{
Utils.WriteLine( ">>> MyLispFunction( {0}, {1} )", a, b );
}
public static void MyLispFunction( string a, string b )
{
Utils.WriteLine( ">>> MyLispFunction( {0}, {1} )", a, b );
}
public static void MyLispFunction( double a, ObjectId b, string c )
{
Utils.WriteLine( ">>> MyLispFunction( {0}, {1}, {2} )", a, b, c );
}
public static void MyLispFunction( string a, SelectionSet b )
{
Utils.WriteLine( ">>> MyLispFunction( {0}, {1} )", a, b );
}
public static void MyLispFunction()
{
Utils.WriteLine( ">>> MyLispFunction()" );
}
/// To invoke this method you must pass the symbol T or NIL
/// in the second argument. Contrary to LISP conventions,
/// no other value is accepted (in LISP, Nil = 'false' and
/// anything else implies 'true', but that convention can
/// not be used here without opening a pandora's box that
/// could lead to selecting the wrong overload).
public static void MyLispFunction( string a, bool b )
{
Utils.WriteLine( ">>> MyLispFunction( {0}, {1} )", a, b );
}
}
public static class DynamicDispatcher
{
const BindingFlags bindingFlags = BindingFlags.Public | BindingFlags.NonPublic
| BindingFlags.Static | BindingFlags.FlattenHierarchy;
public static object Dispatch( this ResultBuffer args, MethodBase baseMethod )
{
if( baseMethod == null )
throw new ArgumentNullException
( "baseMethod" ); // Validate and pre-process the arguments:
TypedValue
[] array
= args
!= null ? args
.Cast<TypedValue
>().Select( Validate
).ToArray() : new TypedValue
[0]; // Try to find a matching overload of the caller:
MethodInfo method = baseMethod.DeclaringType.GetMethod(
baseMethod.Name, bindingFlags, null,
array.Select( tv => tv.Value.GetType() ).ToArray(), null );
if( method == null )
throw new MissingMethodException
( baseMethod
.DeclaringType.Name, baseMethod
.Name ); else
{
object[] values = array != null ? array.Select( t => t.Value ).ToArray() : null;
Utils.WriteLine( "\n\nSelected method: {0}:", method.ToString() );
return method.Invoke( null, values );
}
}
public static T Dispatch<T>( this ResultBuffer args, MethodBase baseMethod )
{
return (T) Dispatch( args, baseMethod );
}
static short RTNIL = (short) LispDataType.Nil;
static short RTT = (short) LispDataType.T_atom;
static short RTLB = (short) LispDataType.ListBegin;
static TypedValue Validate( TypedValue tv )
{
short code = tv.TypeCode;
if( code == RTNIL )
return new TypedValue
( RTNIL,
false ); // interpret NIL as boolean false if( code == RTT )
return new TypedValue
( RTT,
true ); // interpret T as boolean true if( code == RTLB )
throw new ArgumentException
( "lists not supported in this example" ); if( tv.Value == null )
throw new ArgumentException
( "value cannot be null" ); return tv;
}
}
internal static class Utils
{
public static void WriteLine( string fmt, params object[] args )
{
Document doc = Application.DocumentManager.MdiActiveDocument;
if( doc != null )
{
string msg = fmt.StartsWith( "\n" ) ? fmt : "\n" + fmt;
msg = string.Format( msg, args );
doc.Editor.WriteMessage( msg );
}
}
}
/* ;; Tester - needs at least one entity in the current drawing:
(defun dynamic-dispatch-test ()
(dynamic-dispatch "foo")
(dynamic-dispatch "foo" 10)
(dynamic-dispatch "foo" "bar")
(dynamic-dispatch pi (getvar "lastpoint"))
(dynamic-dispatch 2.459 (entlast) "hello")
(dynamic-dispatch "foo" (ssget "x"))
(dynamic-dispatch "foo" T)
(dynamic-dispatch "foo" NIL)
)
*/
}