I have a subclass of DynamicObject, where I implemented several implicit operators (for automatic conversions) and overriden some Try[OperationType] methods.
I get an exception when I try to make a add(+) operation with an object of that type (exception on the bottom of the question).
...
var d = GetDynamicObject();
int result = d + 1;
...
An answer that I thought would help with this question is this one. The behaviour described is correct, but my dynamic object has several static implicit operators defined, and I think the problem is with the string one. When I define the implicit operator for string, que add(+) operation throws an exception. I can observe by the stacktrace that the problem may be that he can't bind the operators, and I'm starting to think thats because some kind of add/concatenation confusion.
I simplified my code and wrote this test that I think it portrays the situation. When I comment the implicit operator for string, eveything works great.
Am I doing something wrong? Is there some method I need to override also? Any ideias?
public class Program
{
static void Main(string[] args)
{
try
{
var d = GetDynamicObject();
int result = d + 1;
Console.WriteLine("OK: " + result);
}
catch (Exception)
{
Console.WriteLine("Error!");
}
Console.ReadLine();
}
public static dynamic GetDynamicObject()
{
return new MyDynamicObject();
}
}
public class MyDynamicObject : DynamicObject
{
public static implicit operator int(MyDynamicObject obj)
{
return 0;
}
public static implicit operator long(MyDynamicObject obj)
{
return 0;
}
public static implicit operator double(MyDynamicObject obj)
{
return 0;
}
// Works when commented
public static implicit operator string(MyDynamicObject obj)
{
return null;
}
public override bool TryBinaryOperation(BinaryOperationBinder binder, object arg, out object result)
{
Console.WriteLine("MyDynamicObject.TryBinaryOperation");
return base.TryBinaryOperation(binder, arg, out result);
}
}
The Exception:
Microsoft.CSharp.RuntimeBinder.RuntimeBinderInternalCompilerException was unhandled
HResult=-2146233088
Message=An unexpected exception occurred while binding a dynamic operation
Source=Microsoft.CSharp
StackTrace:
at Microsoft.CSharp.RuntimeBinder.Semantics.ExpressionBinder.WhichSimpleConversionIsBetter(PredefinedType pt1, PredefinedType pt2)
at Microsoft.CSharp.RuntimeBinder.Semantics.ExpressionBinder.WhichTypeIsBetter(PredefinedType pt1, PredefinedType pt2, CType typeGiven)
at Microsoft.CSharp.RuntimeBinder.Semantics.ExpressionBinder.WhichBofsIsBetter(BinOpFullSig bofs1, BinOpFullSig bofs2, CType type1, CType type2)
at Microsoft.CSharp.RuntimeBinder.Semantics.ExpressionBinder.FindBestSignatureInList(List`1 binopSignatures, BinOpArgInfo info)
at Microsoft.CSharp.RuntimeBinder.Semantics.ExpressionBinder.BindStandardBinop(ExpressionKind ek, EXPR arg1, EXPR arg2)
at Microsoft.CSharp.RuntimeBinder.RuntimeBinder.BindBinaryOperation(CSharpBinaryOperationBinder payload, ArgumentObject[] arguments, Dictionary`2 dictionary)
at Microsoft.CSharp.RuntimeBinder.RuntimeBinder.DispatchPayload(DynamicMetaObjectBinder payload, ArgumentObject[] arguments, Dictionary`2 dictionary)
at Microsoft.CSharp.RuntimeBinder.RuntimeBinder.BindCore(DynamicMetaObjectBinder payload, IEnumerable`1 parameters, DynamicMetaObject[] args, DynamicMetaObject& deferredBinding)
at Microsoft.CSharp.RuntimeBinder.RuntimeBinder.Bind(DynamicMetaObjectBinder payload, IEnumerable`1 parameters, DynamicMetaObject[] args, DynamicMetaObject& deferredBinding)
at Microsoft.CSharp.RuntimeBinder.BinderHelper.Bind(DynamicMetaObjectBinder action, RuntimeBinder binder, IEnumerable`1 args, IEnumerable`1 arginfos, DynamicMetaObject onBindingError)
at Microsoft.CSharp.RuntimeBinder.CSharpBinaryOperationBinder.FallbackBinaryOperation(DynamicMetaObject target, DynamicMetaObject arg, DynamicMetaObject errorSuggestion)
at System.Dynamic.DynamicObject.MetaDynamic.<>c__DisplayClass9_0.<BindBinaryOperation>b__0(DynamicMetaObject e)
at System.Dynamic.DynamicObject.MetaDynamic.CallMethodWithResult(String methodName, DynamicMetaObjectBinder binder, Expression[] args, Fallback fallback, Fallback fallbackInvoke)
at System.Dynamic.DynamicObject.MetaDynamic.BindBinaryOperation(BinaryOperationBinder binder, DynamicMetaObject arg)
at System.Dynamic.BinaryOperationBinder.Bind(DynamicMetaObject target, DynamicMetaObject[] args)
at System.Dynamic.DynamicMetaObjectBinder.Bind(Object[] args, ReadOnlyCollection`1 parameters, LabelTarget returnLabel)
at System.Runtime.CompilerServices.CallSiteBinder.BindCore[T](CallSite`1 site, Object[] args)
at System.Dynamic.UpdateDelegates.UpdateAndExecute2[T0,T1,TRet](CallSite site, T0 arg0, T1 arg1)
at WIG.Common.Tests.Program.Main(String[] args) in C:\WIG\WIG Framework\Dev\WIG.Common.Tests\Program.cs:line 74
at System.AppDomain._nExecuteAssembly(RuntimeAssembly assembly, String[] args)
at System.AppDomain.ExecuteAssembly(String assemblyFile, Evidence assemblySecurity, String[] args)
at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
at System.Threading.ThreadHelper.ThreadStart_Context(Object state)
at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
at System.Threading.ThreadHelper.ThreadStart()
InnerException:
This is a bug in the framework (Edit: Fixed in the corefx codebase, so .NET Core 2.0.4 and higher should not see this issue). We can see the same thing happening with dynmic
without DynamicObject
:
public class Castable
{
public static implicit operator int(Castable obj) => 3;
public static implicit operator string(Castable obj) => "abc";
}
…
dynamic d = new Castable();
var result = d + 2; // Throws RuntimeBinderInternalCompilerException in NetFX
// IndexOutOfRangeException in CoreFX.
Change dynamic
to var
and the static binder is working instead of the dynamic and (not having this bug) sets result
to 5
. Comment out either conversion operator and the dynamic binder can use the other one fine.
The problem is that handling of resolving which conversion to use with +
, which as a built-in operator is used for both addition and string concatenation, hits an off-by-one error.
As you'll know 1 + 2
is treated as an addition and "abc" + x
is treated as string.Concat("abc", x)
. In the case of d + 1
where d
is dynamic
the binder needs to figure out which to use.
When comparing possibilities, some "simple" target types are looked up in a table. If this is possible the enum value for that target will be below a given value, so obviously enough the first thing to do is to see if that is the case.
But unfortunately the check used <=
for that check rather than <
, but the limit compared against is one more than the highest allowed, so <
would have been correct. And the value just above the limit happens to be string
. So if you put the dynamic binder in the situation where it's choosing between a conversion to a "simple" type and a conversion to string, it decides it can compare the two possibilities as both being "simple", tries to find the answer in the lookup table with an index out of range of the array. Depending on the version it does that and blows up with IndexOutOfRangeException
, or it hits an assertion to make sure it isn't doing precisely what it's about to do, and blows up with RuntimeBinderInternalCompilerException
.