diff --git a/.gitignore b/.gitignore
index 26cb01a0..93b7d410 100644
--- a/.gitignore
+++ b/.gitignore
@@ -238,4 +238,5 @@ _Pvt_Extensions
/coverage.xml
/dynamic-coverage-*.xml
/test/**/coverage.net8.0.opencover.xml
+/test/**/coverage.opencover.xml
.nuget/
diff --git a/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs b/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs
index 0ae3e61d..dcf6a329 100644
--- a/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs
+++ b/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs
@@ -680,18 +680,7 @@ private Expression ParseComparisonOperator()
private static bool HasImplicitConversion(Type baseType, Type targetType)
{
- var baseTypeHasConversion = baseType.GetMethods(BindingFlags.Public | BindingFlags.Static)
- .Where(mi => mi.Name == "op_Implicit" && mi.ReturnType == targetType)
- .Any(mi => mi.GetParameters().FirstOrDefault()?.ParameterType == baseType);
-
- if (baseTypeHasConversion)
- {
- return true;
- }
-
- return targetType.GetMethods(BindingFlags.Public | BindingFlags.Static)
- .Where(mi => mi.Name == "op_Implicit" && mi.ReturnType == targetType)
- .Any(mi => mi.GetParameters().FirstOrDefault()?.ParameterType == baseType);
+ return TypeHelper.TryGetImplicitConversionOperatorMethod(baseType, targetType, out _);
}
private static ConstantExpression ParseEnumToConstantExpression(int pos, Type leftType, ConstantExpression constantExpr)
diff --git a/src/System.Linq.Dynamic.Core/Parser/ExpressionPromoter.cs b/src/System.Linq.Dynamic.Core/Parser/ExpressionPromoter.cs
index 49731b24..c82c13f9 100644
--- a/src/System.Linq.Dynamic.Core/Parser/ExpressionPromoter.cs
+++ b/src/System.Linq.Dynamic.Core/Parser/ExpressionPromoter.cs
@@ -135,6 +135,11 @@ public ExpressionPromoter(ParsingConfig config)
return sourceExpression;
}
+ if (TypeHelper.TryGetImplicitConversionOperatorMethod(returnType, type, out _))
+ {
+ return Expression.Convert(sourceExpression, type);
+ }
+
return null;
}
}
\ No newline at end of file
diff --git a/src/System.Linq.Dynamic.Core/Parser/TypeHelper.cs b/src/System.Linq.Dynamic.Core/Parser/TypeHelper.cs
index f4401b63..1cfd533a 100644
--- a/src/System.Linq.Dynamic.Core/Parser/TypeHelper.cs
+++ b/src/System.Linq.Dynamic.Core/Parser/TypeHelper.cs
@@ -533,4 +533,31 @@ public static bool IsDictionary(Type? type)
TryFindGenericType(typeof(IReadOnlyDictionary<,>), type, out _);
#endif
}
+
+ ///
+ /// Check for implicit conversion operators (op_Implicit) from returnType to type.
+ /// Look for op_Implicit on the source type or the target type.
+ ///
+ public static bool TryGetImplicitConversionOperatorMethod(Type returnType, Type type, [NotNullWhen(true)] out MethodBase? implicitOperator)
+ {
+ const string methodName = "op_Implicit";
+
+ implicitOperator = Find(returnType) ?? Find(type);
+ return implicitOperator != null;
+
+ MethodBase? Find(Type searchType)
+ {
+ return searchType.GetMethods(BindingFlags.Public | BindingFlags.Static)
+ .FirstOrDefault(m =>
+ {
+ if (m.Name != methodName || m.ReturnType != type)
+ {
+ return false;
+ }
+
+ var parameters = m.GetParameters();
+ return parameters.Length == 1 && parameters[0].ParameterType == returnType;
+ });
+ }
+ }
}
\ No newline at end of file
diff --git a/test/System.Linq.Dynamic.Core.Tests/DynamicExpressionParserTests.cs b/test/System.Linq.Dynamic.Core.Tests/DynamicExpressionParserTests.cs
index e301394c..b24a2bd2 100644
--- a/test/System.Linq.Dynamic.Core.Tests/DynamicExpressionParserTests.cs
+++ b/test/System.Linq.Dynamic.Core.Tests/DynamicExpressionParserTests.cs
@@ -3,6 +3,7 @@
using System.Linq.Dynamic.Core.Config;
using System.Linq.Dynamic.Core.CustomTypeProviders;
using System.Linq.Dynamic.Core.Exceptions;
+using System.Linq.Dynamic.Core.Parser;
using System.Linq.Dynamic.Core.Tests.Helpers;
using System.Linq.Dynamic.Core.Tests.Helpers.Models;
using System.Linq.Dynamic.Core.Tests.TestHelpers;
@@ -270,6 +271,37 @@ public override string ToString()
}
}
+ [DynamicLinqType]
+ public static class MyMethodsWithImplicitOperatorSupport
+ {
+ public static string UsesMyStructWithImplicitOperator(MyStructWithImplicitOperator myStruct)
+ {
+ return myStruct.Value;
+ }
+ }
+
+ public readonly struct MyStructWithImplicitOperator
+ {
+ private readonly string _value;
+
+ public MyStructWithImplicitOperator(string value)
+ {
+ _value = value;
+ }
+
+ public static implicit operator MyStructWithImplicitOperator(string value)
+ {
+ return new MyStructWithImplicitOperator(value);
+ }
+
+ public static implicit operator string(MyStructWithImplicitOperator myStruct)
+ {
+ return myStruct._value;
+ }
+
+ public string Value => _value;
+ }
+
internal class TestClass794
{
public byte ByteValue { get; set; }
@@ -1517,6 +1549,23 @@ public void DynamicExpressionParser_ParseLambda_With_One_Way_Implicit_Conversion
Assert.NotNull(lambda);
}
+ [Fact]
+ public void DynamicExpressionParser_ParseLambda_With_Implicit_Operator_In_Method_Argument()
+ {
+ // Arrange - Method takes a MyStructWithImplicitOperator but we pass a string literal
+ var expression = $"{nameof(MyMethodsWithImplicitOperatorSupport)}.{nameof(MyMethodsWithImplicitOperatorSupport.UsesMyStructWithImplicitOperator)}(\"Foo\")";
+
+ // Act
+ var parser = new ExpressionParser(parameters: [], expression, values: [], ParsingConfig.Default);
+ var parsedExpression = parser.Parse(typeof(string));
+ var lambda = Expression.Lambda>(parsedExpression);
+ var method = lambda.Compile();
+ var result = method();
+
+ // Assert
+ Assert.Equal("Foo", result);
+ }
+
[Fact]
public void DynamicExpressionParser_ParseLambda_StaticClassWithStaticPropertyWithSameNameAsNormalProperty()
{