1 /* ****************************************************************************
3 * Copyright (c) Microsoft Corporation.
5 * This source code is subject to terms and conditions of the Microsoft Public License. A
6 * copy of the license can be found in the License.html file at the root of this distribution. If
7 * you cannot locate the Microsoft Public License, please send an email to
8 * dlr@microsoft.com. By using this source code in any fashion, you are agreeing to be bound
9 * by the terms of the Microsoft Public License.
11 * You must not remove this notice, or any other, from this software.
14 * ***************************************************************************/
15 using System; using Microsoft;
18 using System.Diagnostics;
20 using System.Dynamic.Utils;
21 using System.Linq.Expressions;
23 using Microsoft.Scripting.Utils;
24 using Microsoft.Linq.Expressions;
26 using System.Reflection;
29 namespace System.Dynamic {
31 namespace Microsoft.Scripting {
34 /// Provides a simple class that can be inherited from to create an object with dynamic behavior
35 /// at runtime. Subclasses can override the various binder methods (GetMember, SetMember, Call, etc...)
36 /// to provide custom behavior that will be invoked at runtime.
38 /// If a method is not overridden then the DynamicObject does not directly support that behavior and
39 /// the call site will determine how the binding should be performed.
41 public class DynamicObject : IDynamicMetaObjectProvider {
44 /// Enables derived types to create a new instance of DynamicObject. DynamicObject instances cannot be
45 /// directly instantiated because they have no implementation of dynamic behavior.
47 protected DynamicObject() {
50 #region Public Virtual APIs
53 /// Provides the implementation of getting a member. Derived classes can override
54 /// this method to customize behavior. When not overridden the call site requesting the
55 /// binder determines the behavior.
57 /// <param name="binder">The binder provided by the call site.</param>
58 /// <param name="result">The result of the get operation.</param>
59 /// <returns>true if the operation is complete, false if the call site should determine behavior.</returns>
60 [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1007:UseGenericsWhereAppropriate")]
61 public virtual bool TryGetMember(GetMemberBinder binder, out object result) {
67 /// Provides the implementation of setting a member. Derived classes can override
68 /// this method to customize behavior. When not overridden the call site requesting the
69 /// binder determines the behavior.
71 /// <param name="binder">The binder provided by the call site.</param>
72 /// <param name="value">The value to set.</param>
73 /// <returns>true if the operation is complete, false if the call site should determine behavior.</returns>
74 public virtual bool TrySetMember(SetMemberBinder binder, object value) {
79 /// Provides the implementation of deleting a member. Derived classes can override
80 /// this method to customize behavior. When not overridden the call site requesting the
81 /// binder determines the behavior.
83 /// <param name="binder">The binder provided by the call site.</param>
84 /// <returns>true if the operation is complete, false if the call site should determine behavior.</returns>
85 public virtual bool TryDeleteMember(DeleteMemberBinder binder) {
90 /// Provides the implementation of calling a member. Derived classes can override
91 /// this method to customize behavior. When not overridden the call site requesting the
92 /// binder determines the behavior.
94 /// <param name="binder">The binder provided by the call site.</param>
95 /// <param name="args">The arguments to be used for the invocation.</param>
96 /// <param name="result">The result of the invocation.</param>
97 /// <returns>true if the operation is complete, false if the call site should determine behavior.</returns>
98 [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1007:UseGenericsWhereAppropriate")]
99 public virtual bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result) {
105 /// Provides the implementation of converting the DynamicObject to another type. Derived classes
106 /// can override this method to customize behavior. When not overridden the call site
107 /// requesting the binder determines the behavior.
109 /// <param name="binder">The binder provided by the call site.</param>
110 /// <param name="result">The result of the conversion.</param>
111 /// <returns>true if the operation is complete, false if the call site should determine behavior.</returns>
112 [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1007:UseGenericsWhereAppropriate")]
113 public virtual bool TryConvert(ConvertBinder binder, out object result) {
119 /// Provides the implementation of creating an instance of the DynamicObject. Derived classes
120 /// can override this method to customize behavior. When not overridden the call site requesting
121 /// the binder determines the behavior.
123 /// <param name="binder">The binder provided by the call site.</param>
124 /// <param name="args">The arguments used for creation.</param>
125 /// <param name="result">The created instance.</param>
126 /// <returns>true if the operation is complete, false if the call site should determine behavior.</returns>
127 [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1007:UseGenericsWhereAppropriate")]
128 public virtual bool TryCreateInstance(CreateInstanceBinder binder, object[] args, out object result) {
134 /// Provides the implementation of invoking the DynamicObject. Derived classes can
135 /// override this method to customize behavior. When not overridden the call site requesting
136 /// the binder determines the behavior.
138 /// <param name="binder">The binder provided by the call site.</param>
139 /// <param name="args">The arguments to be used for the invocation.</param>
140 /// <param name="result">The result of the invocation.</param>
141 /// <returns>true if the operation is complete, false if the call site should determine behavior.</returns>
142 [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1007:UseGenericsWhereAppropriate")]
143 public virtual bool TryInvoke(InvokeBinder binder, object[] args, out object result) {
149 /// Provides the implementation of performing a binary operation. Derived classes can
150 /// override this method to customize behavior. When not overridden the call site requesting
151 /// the binder determines the behavior.
153 /// <param name="binder">The binder provided by the call site.</param>
154 /// <param name="arg">The right operand for the operation.</param>
155 /// <param name="result">The result of the operation.</param>
156 /// <returns>true if the operation is complete, false if the call site should determine behavior.</returns>
157 [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1007:UseGenericsWhereAppropriate")]
158 public virtual bool TryBinaryOperation(BinaryOperationBinder binder, object arg, out object result) {
164 /// Provides the implementation of performing a unary operation. Derived classes can
165 /// override this method to customize behavior. When not overridden the call site requesting
166 /// the binder determines the behavior.
168 /// <param name="binder">The binder provided by the call site.</param>
169 /// <param name="result">The result of the operation.</param>
170 /// <returns>true if the operation is complete, false if the call site should determine behavior.</returns>
171 [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1007:UseGenericsWhereAppropriate")]
172 public virtual bool TryUnaryOperation(UnaryOperationBinder binder, out object result) {
178 /// Provides the implementation of performing a get index operation. Derived classes can
179 /// override this method to customize behavior. When not overridden the call site requesting
180 /// the binder determines the behavior.
182 /// <param name="binder">The binder provided by the call site.</param>
183 /// <param name="indexes">The indexes to be used.</param>
184 /// <param name="result">The result of the operation.</param>
185 /// <returns>true if the operation is complete, false if the call site should determine behavior.</returns>
186 [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1007:UseGenericsWhereAppropriate")]
187 public virtual bool TryGetIndex(GetIndexBinder binder, object[] indexes, out object result) {
193 /// Provides the implementation of performing a set index operation. Derived classes can
194 /// override this method to custmize behavior. When not overridden the call site requesting
195 /// the binder determines the behavior.
197 /// <param name="binder">The binder provided by the call site.</param>
198 /// <param name="indexes">The indexes to be used.</param>
199 /// <param name="value">The value to set.</param>
200 /// <returns>true if the operation is complete, false if the call site should determine behavior.</returns>
201 [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1007:UseGenericsWhereAppropriate")]
202 public virtual bool TrySetIndex(SetIndexBinder binder, object[] indexes, object value) {
207 /// Provides the implementation of performing a delete index operation. Derived classes
208 /// can override this method to custmize behavior. When not overridden the call site
209 /// requesting the binder determines the behavior.
211 /// <param name="binder">The binder provided by the call site.</param>
212 /// <param name="indexes">The indexes to be deleted.</param>
213 /// <returns>true if the operation is complete, false if the call site should determine behavior.</returns>
214 public virtual bool TryDeleteIndex(DeleteIndexBinder binder, object[] indexes) {
219 /// Returns the enumeration of all dynamic member names.
221 /// <returns>The list of dynamic member names.</returns>
222 [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate")]
223 public virtual System.Collections.Generic.IEnumerable<string> GetDynamicMemberNames() {
224 return new string[0];
230 private sealed class MetaDynamic : DynamicMetaObject {
232 internal MetaDynamic(Expression expression, DynamicObject value)
233 : base(expression, BindingRestrictions.Empty, value) {
236 public override System.Collections.Generic.IEnumerable<string> GetDynamicMemberNames()
238 return Value.GetDynamicMemberNames();
241 public override DynamicMetaObject BindGetMember(GetMemberBinder binder) {
242 if (IsOverridden("TryGetMember")) {
243 return CallMethodWithResult("TryGetMember", binder, NoArgs, (e) => binder.FallbackGetMember(this, e));
246 return base.BindGetMember(binder);
249 public override DynamicMetaObject BindSetMember(SetMemberBinder binder, DynamicMetaObject value) {
250 if (IsOverridden("TrySetMember")) {
251 return CallMethodReturnLast("TrySetMember", binder, GetArgs(value), (e) => binder.FallbackSetMember(this, value, e));
254 return base.BindSetMember(binder, value);
257 public override DynamicMetaObject BindDeleteMember(DeleteMemberBinder binder) {
258 if (IsOverridden("TryDeleteMember")) {
259 return CallMethodNoResult("TryDeleteMember", binder, NoArgs, (e) => binder.FallbackDeleteMember(this, e));
262 return base.BindDeleteMember(binder);
265 public override DynamicMetaObject BindConvert(ConvertBinder binder) {
266 if (IsOverridden("TryConvert")) {
267 return CallMethodWithResult("TryConvert", binder, NoArgs, (e) => binder.FallbackConvert(this, e));
270 return base.BindConvert(binder);
273 public override DynamicMetaObject BindInvokeMember(InvokeMemberBinder binder, DynamicMetaObject[] args) {
274 if (IsOverridden("TryInvokeMember")) {
275 return CallMethodWithResult("TryInvokeMember", binder, GetArgArray(args), (e) => binder.FallbackInvokeMember(this, args, e));
276 } else if (IsOverridden("TryGetMember")) {
277 // Generate a tree like:
281 // TryGetMember(payload, out result) ? FallbackInvoke(result) : fallbackResult
284 // Then it calls FallbackInvokeMember with this tree as the
285 // "error", giving the language the option of using this
286 // tree or doing .NET binding.
288 return CallMethodWithResult(
289 "TryGetMember", new GetBinderAdapter(binder), NoArgs,
290 (e) => binder.FallbackInvokeMember(this, args, e),
291 (e) => binder.FallbackInvoke(e, args, null)
295 return base.BindInvokeMember(binder, args);
299 public override DynamicMetaObject BindCreateInstance(CreateInstanceBinder binder, DynamicMetaObject[] args) {
300 if (IsOverridden("TryCreateInstance")) {
301 return CallMethodWithResult("TryCreateInstance", binder, GetArgArray(args), (e) => binder.FallbackCreateInstance(this, args, e));
304 return base.BindCreateInstance(binder, args);
307 public override DynamicMetaObject BindInvoke(InvokeBinder binder, DynamicMetaObject[] args) {
308 if (IsOverridden("TryInvoke")) {
309 return CallMethodWithResult("TryInvoke", binder, GetArgArray(args), (e) => binder.FallbackInvoke(this, args, e));
312 return base.BindInvoke(binder, args);
315 public override DynamicMetaObject BindBinaryOperation(BinaryOperationBinder binder, DynamicMetaObject arg) {
316 if (IsOverridden("TryBinaryOperation")) {
317 return CallMethodWithResult("TryBinaryOperation", binder, GetArgs(arg), (e) => binder.FallbackBinaryOperation(this, arg, e));
320 return base.BindBinaryOperation(binder, arg);
323 public override DynamicMetaObject BindUnaryOperation(UnaryOperationBinder binder) {
324 if (IsOverridden("TryUnaryOperation")) {
325 return CallMethodWithResult("TryUnaryOperation", binder, NoArgs, (e) => binder.FallbackUnaryOperation(this, e));
328 return base.BindUnaryOperation(binder);
331 public override DynamicMetaObject BindGetIndex(GetIndexBinder binder, DynamicMetaObject[] indexes) {
332 if (IsOverridden("TryGetIndex")) {
333 return CallMethodWithResult("TryGetIndex", binder, GetArgArray(indexes), (e) => binder.FallbackGetIndex(this, indexes, e));
336 return base.BindGetIndex(binder, indexes);
339 public override DynamicMetaObject BindSetIndex(SetIndexBinder binder, DynamicMetaObject[] indexes, DynamicMetaObject value) {
340 if (IsOverridden("TrySetIndex")) {
341 return CallMethodReturnLast("TrySetIndex", binder, GetArgArray(indexes, value), (e) => binder.FallbackSetIndex(this, indexes, value, e));
344 return base.BindSetIndex(binder, indexes, value);
347 public override DynamicMetaObject BindDeleteIndex(DeleteIndexBinder binder, DynamicMetaObject[] indexes) {
348 if (IsOverridden("TryDeleteIndex")) {
349 return CallMethodNoResult("TryDeleteIndex", binder, GetArgArray(indexes), (e) => binder.FallbackDeleteIndex(this, indexes, e));
352 return base.BindDeleteIndex(binder, indexes);
355 private delegate DynamicMetaObject Fallback(DynamicMetaObject errorSuggestion);
357 private readonly static Expression[] NoArgs = new Expression[0];
359 private static Expression[] GetArgs(params DynamicMetaObject[] args) {
360 Expression[] paramArgs = DynamicMetaObject.GetExpressions(args);
362 for (int i = 0; i < paramArgs.Length; i++) {
363 paramArgs[i] = Expression.Convert(args[i].Expression, typeof(object));
369 private static Expression[] GetArgArray(DynamicMetaObject[] args) {
370 return new[] { Expression.NewArrayInit(typeof(object), GetArgs(args)) };
373 private static Expression[] GetArgArray(DynamicMetaObject[] args, DynamicMetaObject value) {
374 return new Expression[] {
375 Expression.NewArrayInit(typeof(object), GetArgs(args)),
376 Expression.Convert(value.Expression, typeof(object))
380 private static ConstantExpression Constant(DynamicMetaObjectBinder binder) {
381 Type t = binder.GetType();
382 while (!t.IsVisible) {
385 return Expression.Constant(binder, t);
389 /// Helper method for generating a MetaObject which calls a
390 /// specific method on Dynamic that returns a result
392 private DynamicMetaObject CallMethodWithResult(string methodName, DynamicMetaObjectBinder binder, Expression[] args, Fallback fallback) {
393 return CallMethodWithResult(methodName, binder, args, fallback, null);
397 /// Helper method for generating a MetaObject which calls a
398 /// specific method on Dynamic that returns a result
400 private DynamicMetaObject CallMethodWithResult(string methodName, DynamicMetaObjectBinder binder, Expression[] args, Fallback fallback, Fallback fallbackInvoke) {
402 // First, call fallback to do default binding
403 // This produces either an error or a call to a .NET member
405 DynamicMetaObject fallbackResult = fallback(null);
408 // Build a new expression like:
411 // TryGetMember(payload, out result) ? fallbackInvoke(result) : fallbackResult
414 var result = Expression.Parameter(typeof(object), null);
416 var callArgs = new Expression[args.Length + 2];
417 Array.Copy(args, 0, callArgs, 1, args.Length);
418 callArgs[0] = Constant(binder);
419 callArgs[callArgs.Length - 1] = result;
421 var resultMO = new DynamicMetaObject(result, BindingRestrictions.Empty);
423 // Need to add a conversion if calling TryConvert
424 if (binder.ReturnType != typeof(object)) {
425 Debug.Assert(binder is ConvertBinder && fallbackInvoke == null);
427 var convert = Expression.Convert(resultMO.Expression, binder.ReturnType);
428 // will always be a cast or unbox
429 Debug.Assert(convert.Method == null);
431 resultMO = new DynamicMetaObject(convert, resultMO.Restrictions);
434 if (fallbackInvoke != null) {
435 resultMO = fallbackInvoke(resultMO);
438 var callDynamic = new DynamicMetaObject(
441 Expression.Condition(
444 typeof(DynamicObject).GetMethod(methodName),
448 fallbackResult.Expression,
452 GetRestrictions().Merge(resultMO.Restrictions).Merge(fallbackResult.Restrictions)
456 // Now, call fallback again using our new MO as the error
457 // When we do this, one of two things can happen:
458 // 1. Binding will succeed, and it will ignore our call to
459 // the dynamic method, OR
460 // 2. Binding will fail, and it will use the MO we created
463 return fallback(callDynamic);
468 /// Helper method for generating a MetaObject which calls a
469 /// specific method on Dynamic, but uses one of the arguments for
472 private DynamicMetaObject CallMethodReturnLast(string methodName, DynamicMetaObjectBinder binder, Expression[] args, Fallback fallback) {
474 // First, call fallback to do default binding
475 // This produces either an error or a call to a .NET member
477 DynamicMetaObject fallbackResult = fallback(null);
480 // Build a new expression like:
483 // TrySetMember(payload, result = value) ? result : fallbackResult
487 var result = Expression.Parameter(typeof(object), null);
488 var callArgs = args.AddFirst(Constant(binder));
489 callArgs[args.Length] = Expression.Assign(result, callArgs[args.Length]);
491 var callDynamic = new DynamicMetaObject(
494 Expression.Condition(
497 typeof(DynamicObject).GetMethod(methodName),
501 fallbackResult.Expression,
505 GetRestrictions().Merge(fallbackResult.Restrictions)
509 // Now, call fallback again using our new MO as the error
510 // When we do this, one of two things can happen:
511 // 1. Binding will succeed, and it will ignore our call to
512 // the dynamic method, OR
513 // 2. Binding will fail, and it will use the MO we created
516 return fallback(callDynamic);
521 /// Helper method for generating a MetaObject which calls a
522 /// specific method on Dynamic, but uses one of the arguments for
525 private DynamicMetaObject CallMethodNoResult(string methodName, DynamicMetaObjectBinder binder, Expression[] args, Fallback fallback) {
527 // First, call fallback to do default binding
528 // This produces either an error or a call to a .NET member
530 DynamicMetaObject fallbackResult = fallback(null);
533 // Build a new expression like:
534 // if (TryDeleteMember(payload)) { } else { fallbackResult }
536 var callDynamic = new DynamicMetaObject(
537 Expression.Condition(
540 typeof(DynamicObject).GetMethod(methodName),
541 args.AddFirst(Constant(binder))
544 fallbackResult.Expression,
547 GetRestrictions().Merge(fallbackResult.Restrictions)
551 // Now, call fallback again using our new MO as the error
552 // When we do this, one of two things can happen:
553 // 1. Binding will succeed, and it will ignore our call to
554 // the dynamic method, OR
555 // 2. Binding will fail, and it will use the MO we created
558 return fallback(callDynamic);
562 /// Checks if the derived type has overridden the specified method. If there is no
563 /// implementation for the method provided then Dynamic falls back to the base class
564 /// behavior which lets the call site determine how the binder is performed.
566 private bool IsOverridden(string method) {
567 var methods = Value.GetType().GetMember(method, MemberTypes.Method, BindingFlags.Public | BindingFlags.Instance);
569 foreach (MethodInfo mi in methods) {
570 if (mi.DeclaringType != typeof(DynamicObject) && mi.GetBaseDefinition().DeclaringType == typeof(DynamicObject)) {
579 /// Returns a Restrictions object which includes our current restrictions merged
580 /// with a restriction limiting our type
582 private BindingRestrictions GetRestrictions() {
583 Debug.Assert(Restrictions == BindingRestrictions.Empty, "We don't merge, restrictions are always empty");
585 return BindingRestrictions.GetTypeRestriction(this);
589 /// Returns our Expression converted to our known LimitType
591 private Expression GetLimitedSelf() {
592 if (TypeUtils.AreEquivalent(Expression.Type, LimitType)) {
595 return Expression.Convert(Expression, LimitType);
598 private new DynamicObject Value {
600 return (DynamicObject)base.Value;
604 // It is okay to throw NotSupported from this binder. This object
605 // is only used by DynamicObject.GetMember--it is not expected to
606 // (and cannot) implement binding semantics. It is just so the DO
607 // can use the Name and IgnoreCase properties.
608 private sealed class GetBinderAdapter : GetMemberBinder {
609 internal GetBinderAdapter(InvokeMemberBinder binder)
610 : base(binder.Name, binder.IgnoreCase) {
613 public override DynamicMetaObject FallbackGetMember(DynamicMetaObject target, DynamicMetaObject errorSuggestion) {
614 throw new NotSupportedException();
621 #region IDynamicMetaObjectProvider Members
624 /// The provided MetaObject will dispatch to the Dynamic virtual methods.
625 /// The object can be encapsulated inside of another MetaObject to
626 /// provide custom behavior for individual actions.
628 public virtual DynamicMetaObject GetMetaObject(Expression parameter) {
629 return new MetaDynamic(parameter, this);