-
-
Notifications
You must be signed in to change notification settings - Fork 379
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implicit conversions in a type not working #248
Comments
In a way, this is a simple ask as the implicit conversion is taken care of by Expression.Convert as shown below private class Convertible<T>
{
private readonly T m_sourceValue;
public Convertible(T sourceValue)
{
m_sourceValue = sourceValue;
}
public T SourceValue { get { return m_sourceValue; } }
public static implicit operator T(Convertible<T> source)
{
return source.SourceValue;
}
public static implicit operator Convertible<T>(T source)
{
return new Convertible<T>(source);
}
}
[Test]
public void ExampleExpressions()
{
var initialValue = Expression.Constant(1);
var assignedValue = Expression.Variable(typeof(Convertible<int>));
var reassignedValue = Expression.Variable(typeof(int));
Expression.Lambda(Expression.Block(new ParameterExpression[] { assignedValue, reassignedValue },
Expression.Assign(assignedValue, Expression.Convert(initialValue, assignedValue.Type)),
Expression.Assign(reassignedValue, Expression.Convert(assignedValue, reassignedValue.Type))
)).Compile().DynamicInvoke();
} Expression.Convert calls the corresponding conversion operator, regardless of the operator being implicit or explicit. [Test]
public void ExampleExpressions()
{
var initialValue = Expression.Constant(1);
var assignedValue = Expression.Variable(typeof(Convertible<int>));
var reassignedValue = Expression.Variable(typeof(Convertible<int>));
var lambda = Expression.Lambda(Expression.Block(new ParameterExpression[] { assignedValue, reassignedValue },
Expression.Assign(assignedValue, Expression.Convert(initialValue, assignedValue.Type)),
Expression.Assign(reassignedValue, Expression.Convert(assignedValue, reassignedValue.Type))
));
var result = lambda.Compile().DynamicInvoke();
} As such, for assignment calls this is trivial as we could always call Convert on the value being assigned. Method lookup is more difficult as shown by the following code [Test]
public void TestMethodLookup()
{
var c = new Convertible<int>(5);
MethodWithBaseType(c);
var reflectionMethod = GetType().GetMethod(nameof(MethodWithBaseType), new Type[] { c.GetType() });
Assert.IsNull(reflectionMethod);
}
public void MethodWithBaseType(int i)
{
} Note how the c# compiler correctly finds the method, but the reflection does not. From some quick investigation of code base, it seems like all that is needed is to update IsCompatibleWith method to be able to look up conversion operators since the Expression.Convert takes care of the actual conversion call. However, the determination of when to perform the conversion can be substantially more difficult once it comes to operators as it is necessary to determine which argument to convert based on which operators are present, or if conversion is not necessary due to operators already existing on the class. In the case of MyCustomType as defined above, anytime an instance of that uses one of the operators defined on the conversion type, it would always be converted. As shown below things can get pretty interesting once you start adding your own operators. public static Convertible<int> operator +(Convertible<T> left, int right)
{
if (left is Convertible<int> ci)
{
return ci.m_sourceValue + (right * 2);
}
throw new NotImplementedException();
}
public static Convertible<int> operator +(int left, Convertible<T> right)
{
if (right is Convertible<int> ci)
{
return ci.m_sourceValue + (left * 3);
}
throw new NotImplementedException();
}
public static Convertible<decimal> operator +(Convertible<T> left, Convertible<T> right)
{
if (right is Convertible<decimal> ci && left is Convertible<decimal> cir)
{
return ci.m_sourceValue + cir.m_sourceValue;
}
throw new NotImplementedException();
}
[Test]
public void OperatorExpressions()
{
var arg1 = new Convertible<int>(5);
var arg2 = 3;
var testConvLeft = arg1 + arg2;
//To prove the compiler automatically lifts it to the result type of the operator
//and we aren't getting any changed results from conversion
Assert.IsInstanceOf(typeof(Convertible<int>), testConvLeft);
//Also to prove we are calling ours.
Assert.AreEqual(11, testConvLeft.SourceValue);
var testConvRight = arg2 + arg1;
Assert.AreEqual(14, testConvRight.SourceValue);
//c# compiler automatically converts down to the implicit conversion type since we don't actually have one defined.
var usingIntOperators = arg1 - arg2;
Assert.AreEqual(2, usingIntOperators);
usingIntOperators = arg2 - arg1;
Assert.AreEqual(usingIntOperators, -2);
var decimalL = new Convertible<decimal>(10m);
var decimalR = new Convertible<decimal>(5m);
Assert.AreEqual(15m, (decimalL + decimalR).SourceValue);
} However, all that noise boils down to the following logic
Point 3 is where things can cause problems/ambiguous operator detection.
This results in a compiler error of "Operator "+" is ambiguous on operands of type 'MyCustomType' and 'MyCustomType'" I think this covers all of the typical use cases where implicit conversions are respected. |
Hi @holdenmai, thanks for the investigation, that's really interesting! I'm not sure we should treat user defined operators any differently than the way we treat other methods. I suspect DynamicExpresso doesn't handle implicit conversion for method call parameters either. I think a good place to start would be WDYT? |
Decided to dig into how the .net standard performs the operator check to see if there was something publicly available. Unfortunately not, however the logic they use to check if there is a conversion is present here: So potentially we could use this logic as well. |
A nice step into this direction would also be to make |
Hello,
First of all, DynamicExpresso is great library. I hate using CodeDom, because it is slow. I hate using Roslyn, because it depends on dozens of other libraries...
I noticed that, DynamicExpresso does not use implicit conversions defined in a custom type.
For example, I have a type like following:
I'm expecting to to be able to execute following expression:
"GetMyCustomType() + 5"
However, expression is not compiled.
I need to add overload every operator (+, -, *, / |, <<, >>, ...) for each basic types into the MyCustomType.
Is it possible to support using the implicit operators in a type?
The text was updated successfully, but these errors were encountered: