using System; using System.Collections.ObjectModel; using System.Linq; using System.Linq.Expressions; using System.Runtime.CompilerServices; using DynamicExpresso.Reflection; using Microsoft.CSharp.RuntimeBinder; namespace DynamicExpresso.Resolution { internal interface IConvertibleToWritableBinder { CallSiteBinder ToWritableBinder(); } internal class LateGetMemberCallSiteBinder : CallSiteBinder, IConvertibleToWritableBinder { private readonly string _propertyOrFieldName; public LateGetMemberCallSiteBinder(string propertyOrFieldName) { _propertyOrFieldName = propertyOrFieldName; } public override Expression Bind(object[] args, ReadOnlyCollection parameters, LabelTarget returnLabel) { // there's only one argument: the instance on which the member is accessed var binder = Binder.GetMember( CSharpBinderFlags.None, _propertyOrFieldName, TypeUtils.RemoveArrayType(args[0]?.GetType()), parameters.Select(x => CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null)) ); return binder.Bind(args, parameters, returnLabel); } public CallSiteBinder ToWritableBinder() { return new LateSetMemberCallSiteBinder(_propertyOrFieldName); } } internal class LateSetMemberCallSiteBinder : CallSiteBinder { private readonly string _propertyOrFieldName; public LateSetMemberCallSiteBinder(string propertyOrFieldName) { _propertyOrFieldName = propertyOrFieldName; } public override Expression Bind(object[] args, ReadOnlyCollection parameters, LabelTarget returnLabel) { // there are two arguments: the instance on which the member is set and the value to set var binder = Binder.SetMember( CSharpBinderFlags.None, _propertyOrFieldName, TypeUtils.RemoveArrayType(args[0]?.GetType()), parameters.Select(x => CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null)) ); return binder.Bind(args, parameters, returnLabel); } } /// /// Binds to a method invocation of an instance as late as possible. This allows the use of anonymous types on dynamic values. /// internal class LateInvokeMethodCallSiteBinder : CallSiteBinder { private readonly string _methodName; private readonly bool _isStatic; public LateInvokeMethodCallSiteBinder(string methodName, bool isStatic) { _methodName = methodName; _isStatic = isStatic; } public override Expression Bind(object[] args, ReadOnlyCollection parameters, LabelTarget returnLabel) { // if the method is static, the first argument is the type containing the method, // otherwise it's the instance on which the method is called var context = _isStatic ? (Type)args[0] : args[0]?.GetType(); var argumentInfo = parameters.Select(x => CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null)).ToArray(); if (_isStatic) { // instruct the compiler that we already know the containing type of the method argumentInfo[0] = CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.UseCompileTimeType | CSharpArgumentInfoFlags.IsStaticType, null); } var binderM = Binder.InvokeMember( CSharpBinderFlags.None, _methodName, null, TypeUtils.RemoveArrayType(context), argumentInfo ); return binderM.Bind(args, parameters, returnLabel); } } /// /// Binds to a delegate invocation as late as possible. This allows the use of delegates with dynamic arguments. /// internal class LateInvokeDelegateCallSiteBinder : CallSiteBinder { public LateInvokeDelegateCallSiteBinder() { } public override Expression Bind(object[] args, ReadOnlyCollection parameters, LabelTarget returnLabel) { var argumentInfo = parameters.Select(x => CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null)).ToArray(); // the first argument is the delegate to invoke: instruct the compiler that we already know its type argumentInfo[0] = CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.UseCompileTimeType, null); var binderM = Binder.Invoke( CSharpBinderFlags.None, null, argumentInfo ); return binderM.Bind(args, parameters, returnLabel); } } /// /// Binds to an items invocation of an instance as late as possible. This allows the use of anonymous types on dynamic values. /// internal class LateGetIndexCallSiteBinder : CallSiteBinder, IConvertibleToWritableBinder { public override Expression Bind(object[] args, ReadOnlyCollection parameters, LabelTarget returnLabel) { // there are two arguments: the instance on which the member is set and the value of the indexer var binder = Binder.GetIndex( CSharpBinderFlags.None, TypeUtils.RemoveArrayType(args[0]?.GetType()), parameters.Select(x => CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null)) ); return binder.Bind(args, parameters, returnLabel); } public CallSiteBinder ToWritableBinder() { return new LateSetIndexCallSiteBinder(); } } internal class LateSetIndexCallSiteBinder : CallSiteBinder { public override Expression Bind(object[] args, ReadOnlyCollection parameters, LabelTarget returnLabel) { // there are three arguments: the instance on which the member is set, the value of the indexer, and the value to set var binder = Binder.SetIndex( CSharpBinderFlags.None, TypeUtils.RemoveArrayType(args[0]?.GetType()), parameters.Select(x => CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null)) ); return binder.Bind(args, parameters, returnLabel); } } }