1 // Licensed to the .NET Foundation under one or more agreements. 2 // The .NET Foundation licenses this file to you under the MIT license. 3 // See the LICENSE file in the project root for more information. 4 5 using System.Collections.ObjectModel; 6 using System.Dynamic.Utils; 7 using System.Linq.Expressions; 8 using System.Runtime.CompilerServices; 9 10 using DelegateHelpers = System.Linq.Expressions.Compiler.DelegateHelpers; 11 12 namespace System.Dynamic 13 { 14 /// <summary> 15 /// The dynamic call site binder that participates in the <see cref="DynamicMetaObject"/> binding protocol. 16 /// </summary> 17 /// <remarks> 18 /// The <see cref="CallSiteBinder"/> performs the binding of the dynamic operation using the runtime values 19 /// as input. On the other hand, the <see cref="DynamicMetaObjectBinder"/> participates in the <see cref="DynamicMetaObject"/> 20 /// binding protocol. 21 /// </remarks> 22 public abstract class DynamicMetaObjectBinder : CallSiteBinder 23 { 24 /// <summary> 25 /// Initializes a new instance of the <see cref="DynamicMetaObjectBinder"/> class. 26 /// </summary> DynamicMetaObjectBinder()27 protected DynamicMetaObjectBinder() 28 { 29 } 30 31 /// <summary> 32 /// The result type of the operation. 33 /// </summary> 34 public virtual Type ReturnType => typeof(object); 35 36 /// <summary> 37 /// Performs the runtime binding of the dynamic operation on a set of arguments. 38 /// </summary> 39 /// <param name="args">An array of arguments to the dynamic operation.</param> 40 /// <param name="parameters">The array of <see cref="ParameterExpression"/> instances that represent the parameters of the call site in the binding process.</param> 41 /// <param name="returnLabel">A LabelTarget used to return the result of the dynamic binding.</param> 42 /// <returns> 43 /// An Expression that performs tests on the dynamic operation arguments, and 44 /// performs the dynamic operation if the tests are valid. If the tests fail on 45 /// subsequent occurrences of the dynamic operation, Bind will be called again 46 /// to produce a new <see cref="Expression"/> for the new argument types. 47 /// </returns> Bind(object[] args, ReadOnlyCollection<ParameterExpression> parameters, LabelTarget returnLabel)48 public sealed override Expression Bind(object[] args, ReadOnlyCollection<ParameterExpression> parameters, LabelTarget returnLabel) 49 { 50 ContractUtils.RequiresNotNull(args, nameof(args)); 51 ContractUtils.RequiresNotNull(parameters, nameof(parameters)); 52 ContractUtils.RequiresNotNull(returnLabel, nameof(returnLabel)); 53 if (args.Length == 0) 54 { 55 throw System.Linq.Expressions.Error.OutOfRange("args.Length", 1); 56 } 57 if (parameters.Count == 0) 58 { 59 throw System.Linq.Expressions.Error.OutOfRange("parameters.Count", 1); 60 } 61 if (args.Length != parameters.Count) 62 { 63 throw new ArgumentOutOfRangeException(nameof(args)); 64 } 65 66 // Ensure that the binder's ReturnType matches CallSite's return 67 // type. We do this so meta objects and language binders can 68 // compose trees together without needing to insert converts. 69 Type expectedResult; 70 if (IsStandardBinder) 71 { 72 expectedResult = ReturnType; 73 74 if (returnLabel.Type != typeof(void) && 75 !TypeUtils.AreReferenceAssignable(returnLabel.Type, expectedResult)) 76 { 77 throw System.Linq.Expressions.Error.BinderNotCompatibleWithCallSite(expectedResult, this, returnLabel.Type); 78 } 79 } 80 else 81 { 82 // Even for non-standard binders, we have to at least make sure 83 // it works with the CallSite's type to build the return. 84 expectedResult = returnLabel.Type; 85 } 86 87 DynamicMetaObject target = DynamicMetaObject.Create(args[0], parameters[0]); 88 DynamicMetaObject[] metaArgs = CreateArgumentMetaObjects(args, parameters); 89 90 DynamicMetaObject binding = Bind(target, metaArgs); 91 92 if (binding == null) 93 { 94 throw System.Linq.Expressions.Error.BindingCannotBeNull(); 95 } 96 97 Expression body = binding.Expression; 98 BindingRestrictions restrictions = binding.Restrictions; 99 100 // Ensure the result matches the expected result type. 101 if (expectedResult != typeof(void) && 102 !TypeUtils.AreReferenceAssignable(expectedResult, body.Type)) 103 { 104 // 105 // Blame the last person that handled the result: assume it's 106 // the dynamic object (if any), otherwise blame the language. 107 // 108 if (target.Value is IDynamicMetaObjectProvider) 109 { 110 throw System.Linq.Expressions.Error.DynamicObjectResultNotAssignable(body.Type, target.Value.GetType(), this, expectedResult); 111 } 112 else 113 { 114 throw System.Linq.Expressions.Error.DynamicBinderResultNotAssignable(body.Type, this, expectedResult); 115 } 116 } 117 118 // if the target is IDO, standard binders ask it to bind the rule so we may have a target-specific binding. 119 // it makes sense to restrict on the target's type in such cases. 120 // ideally IDO metaobjects should do this, but they often miss that type of "this" is significant. 121 if (IsStandardBinder && args[0] as IDynamicMetaObjectProvider != null) 122 { 123 if (restrictions == BindingRestrictions.Empty) 124 { 125 throw System.Linq.Expressions.Error.DynamicBindingNeedsRestrictions(target.Value.GetType(), this); 126 } 127 } 128 129 // Add the return 130 if (body.NodeType != ExpressionType.Goto) 131 { 132 body = Expression.Return(returnLabel, body); 133 } 134 135 // Finally, add restrictions 136 if (restrictions != BindingRestrictions.Empty) 137 { 138 body = Expression.IfThen(restrictions.ToExpression(), body); 139 } 140 141 return body; 142 } 143 CreateArgumentMetaObjects(object[] args, ReadOnlyCollection<ParameterExpression> parameters)144 private static DynamicMetaObject[] CreateArgumentMetaObjects(object[] args, ReadOnlyCollection<ParameterExpression> parameters) 145 { 146 DynamicMetaObject[] mos; 147 if (args.Length != 1) 148 { 149 mos = new DynamicMetaObject[args.Length - 1]; 150 for (int i = 1; i < args.Length; i++) 151 { 152 mos[i - 1] = DynamicMetaObject.Create(args[i], parameters[i]); 153 } 154 } 155 else 156 { 157 mos = DynamicMetaObject.EmptyMetaObjects; 158 } 159 return mos; 160 } 161 162 /// <summary> 163 /// When overridden in the derived class, performs the binding of the dynamic operation. 164 /// </summary> 165 /// <param name="target">The target of the dynamic operation.</param> 166 /// <param name="args">An array of arguments of the dynamic operation.</param> 167 /// <returns>The <see cref="DynamicMetaObject"/> representing the result of the binding.</returns> Bind(DynamicMetaObject target, DynamicMetaObject[] args)168 public abstract DynamicMetaObject Bind(DynamicMetaObject target, DynamicMetaObject[] args); 169 170 /// <summary> 171 /// Gets an expression that will cause the binding to be updated. It 172 /// indicates that the expression's binding is no longer valid. 173 /// This is typically used when the "version" of a dynamic object has 174 /// changed. 175 /// </summary> 176 /// <param name="type">The <see cref="Expression.Type">Type</see> property of the resulting expression; any type is allowed.</param> 177 /// <returns>The update expression.</returns> 178 [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic")] GetUpdateExpression(Type type)179 public Expression GetUpdateExpression(Type type) 180 { 181 return Expression.Goto(CallSiteBinder.UpdateLabel, type); 182 } 183 184 /// <summary> 185 /// Defers the binding of the operation until later time when the runtime values of all dynamic operation arguments have been computed. 186 /// </summary> 187 /// <param name="target">The target of the dynamic operation.</param> 188 /// <param name="args">An array of arguments of the dynamic operation.</param> 189 /// <returns>The <see cref="DynamicMetaObject"/> representing the result of the binding.</returns> Defer(DynamicMetaObject target, params DynamicMetaObject[] args)190 public DynamicMetaObject Defer(DynamicMetaObject target, params DynamicMetaObject[] args) 191 { 192 ContractUtils.RequiresNotNull(target, nameof(target)); 193 194 if (args == null) 195 { 196 return MakeDeferred(target.Restrictions, target); 197 } 198 else 199 { 200 return MakeDeferred( 201 target.Restrictions.Merge(BindingRestrictions.Combine(args)), 202 args.AddFirst(target) 203 ); 204 } 205 } 206 207 /// <summary> 208 /// Defers the binding of the operation until later time when the runtime values of all dynamic operation arguments have been computed. 209 /// </summary> 210 /// <param name="args">An array of arguments of the dynamic operation.</param> 211 /// <returns>The <see cref="DynamicMetaObject"/> representing the result of the binding.</returns> Defer(params DynamicMetaObject[] args)212 public DynamicMetaObject Defer(params DynamicMetaObject[] args) 213 { 214 return MakeDeferred(BindingRestrictions.Combine(args), args); 215 } 216 MakeDeferred(BindingRestrictions rs, params DynamicMetaObject[] args)217 private DynamicMetaObject MakeDeferred(BindingRestrictions rs, params DynamicMetaObject[] args) 218 { 219 var exprs = DynamicMetaObject.GetExpressions(args); 220 221 Type delegateType = DelegateHelpers.MakeDeferredSiteDelegate(args, ReturnType); 222 223 // Because we know the arguments match the delegate type (we just created the argument types) 224 // we go directly to DynamicExpression.Make to avoid a bunch of unnecessary argument validation 225 return new DynamicMetaObject( 226 DynamicExpression.Make(ReturnType, delegateType, this, new TrueReadOnlyCollection<Expression>(exprs)), 227 rs 228 ); 229 } 230 231 /// <summary> 232 /// Returns <c>true</c> for standard <see cref="DynamicMetaObjectBinder"/>s; otherwise, <c>false</c>. 233 /// </summary> 234 internal virtual bool IsStandardBinder => false; 235 } 236 } 237