Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions src/System.Linq.Dynamic.Core/DynamicClass.cs
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,16 @@ public object? this[string name]
}
}

/// <summary>
/// Determines whether a property with the specified name exists in the collection.
/// </summary>
/// <param name="name">The name of the property to locate. Cannot be null.</param>
/// <returns><c>true</c> if a property with the specified name exists; otherwise, <c>false</c>.</returns>
public bool ContainsProperty(string name)
{
return Properties.ContainsKey(name);
}

/// <summary>
/// Returns the enumeration of all dynamic member names.
/// </summary>
Expand Down
14 changes: 12 additions & 2 deletions src/System.Linq.Dynamic.Core/DynamicClass.uap.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ public class DynamicClass : DynamicObject
{
internal const string IndexerName = "System_Linq_Dynamic_Core_DynamicClass_Indexer";

private readonly Dictionary<string, object> _properties = new();
private readonly Dictionary<string, object?> _properties = new();

/// <summary>
/// Initializes a new instance of the <see cref="DynamicClass"/> class.
Expand All @@ -35,7 +35,7 @@ public DynamicClass(params KeyValuePair<string, object>[] propertylist)
/// <param name="name">The name.</param>
/// <returns>Value from the property.</returns>
[IndexerName(IndexerName)]
public object this[string name]
public object? this[string name]
{
get
{
Expand All @@ -59,6 +59,16 @@ public object this[string name]
}
}

/// <summary>
/// Determines whether a property with the specified name exists in the collection.
/// </summary>
/// <param name="name">The name of the property to locate. Cannot be null.</param>
/// <returns><c>true</c> if a property with the specified name exists; otherwise, <c>false</c>.</returns>
public bool ContainsProperty(string name)
{
return _properties.ContainsKey(name);
}

/// <summary>
/// Returns the enumeration of all dynamic member names.
/// </summary>
Expand Down
50 changes: 47 additions & 3 deletions src/System.Linq.Dynamic.Core/Parser/ExpressionHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ namespace System.Linq.Dynamic.Core.Parser;

internal class ExpressionHelper : IExpressionHelper
{
private static readonly Expression _nullExpression = Expression.Constant(null);
private readonly IConstantExpressionWrapper _constantExpressionWrapper = new ConstantExpressionWrapper();
private readonly ParsingConfig _parsingConfig;

Expand Down Expand Up @@ -340,7 +341,7 @@ public bool TryGenerateAndAlsoNotNullExpression(Expression sourceExpression, boo
// Convert all expressions into '!= null' expressions (only if the type can be null)
var binaryExpressions = expressions
.Where(expression => TypeHelper.TypeCanBeNull(expression.Type))
.Select(expression => Expression.NotEqual(expression, Expression.Constant(null)))
.Select(expression => Expression.NotEqual(expression, _nullExpression))
.ToArray();

// Convert all binary expressions into `AndAlso(...)`
Expand Down Expand Up @@ -393,16 +394,46 @@ public bool TryConvertTypes(ref Expression left, ref Expression right)

if (left.Type == typeof(object))
{
if (TryGetAsIndexerExpression(left, out var ce))
{
var rightTypeAsNullableType = TypeHelper.GetNullableType(right.Type);

right = Expression.Convert(right, rightTypeAsNullableType);

left = Expression.Condition(
ce.Test,
Expression.Convert(ce.IfTrue, rightTypeAsNullableType),
Expression.Convert(_nullExpression, rightTypeAsNullableType)
);

return true;
}

left = Expression.Condition(
Expression.Equal(left, Expression.Constant(null, typeof(object))),
Expression.Equal(left, _nullExpression),
GenerateDefaultExpression(right.Type),
Expression.Convert(left, right.Type)
);
}
else if (right.Type == typeof(object))
{
if (TryGetAsIndexerExpression(right, out var ce))
{
var leftTypeAsNullableType = TypeHelper.GetNullableType(left.Type);

left = Expression.Convert(left, leftTypeAsNullableType);

right = Expression.Condition(
ce.Test,
Expression.Convert(ce.IfTrue, leftTypeAsNullableType),
Expression.Convert(_nullExpression, leftTypeAsNullableType)
);

return true;
}

right = Expression.Condition(
Expression.Equal(right, Expression.Constant(null, typeof(object))),
Expression.Equal(right, _nullExpression),
GenerateDefaultExpression(left.Type),
Expression.Convert(right, left.Type)
);
Expand Down Expand Up @@ -546,4 +577,17 @@ private static object[] ConvertIfIEnumerableHasValues(IEnumerable? input)

return [];
}

private static bool TryGetAsIndexerExpression(Expression expression, [NotNullWhen(true)] out ConditionalExpression? indexerExpresion)
{
indexerExpresion = expression as ConditionalExpression;
if (indexerExpresion == null)
{
return false;
}

return
indexerExpresion.IfTrue.ToString().Contains(DynamicClass.IndexerName) &&
indexerExpresion.Test.ToString().Contains("ContainsProperty");
}
}
28 changes: 26 additions & 2 deletions src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1921,11 +1921,35 @@

if (!_parsingConfig.DisableMemberAccessToIndexAccessorFallback && extraCheck)
{
var indexerName = TypeHelper.IsDynamicClass(type!) ? DynamicClass.IndexerName : "Item";
var isDynamicClass = TypeHelper.IsDynamicClass(type!);
var indexerName = isDynamicClass ? DynamicClass.IndexerName : "Item";

// Try to get the indexer property "Item" or "DynamicClass_Indexer" which takes a string as parameter
var indexerMethod = expression?.Type.GetMethod($"get_{indexerName}", [typeof(string)]);
if (indexerMethod != null)
{
return Expression.Call(expression, indexerMethod, Expression.Constant(id));
if (!isDynamicClass)
{
return Expression.Call(expression, indexerMethod, Expression.Constant(id));
}

var containsPropertyMethod = typeof(DynamicClass).GetMethod("ContainsProperty");
if (containsPropertyMethod == null)
{
return Expression.Call(expression, indexerMethod, Expression.Constant(id));
}

var callContainsPropertyExpression = Expression.Call(
expression!,
containsPropertyMethod,
Expression.Constant(id)
);

return Expression.Condition(
Expression.Equal(callContainsPropertyExpression, Expression.Constant(true)),
Expression.Call(expression, indexerMethod, Expression.Constant(id)),
Expression.Constant(null)
);
}
}

Expand Down Expand Up @@ -1956,7 +1980,7 @@
switch (member)
{
case PropertyInfo property:
var propertyIsStatic = property?.GetGetMethod().IsStatic ?? property?.GetSetMethod().IsStatic ?? false;

Check warning on line 1983 in src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs

View workflow job for this annotation

GitHub Actions / Linux: Build and Tests

Dereference of a possibly null reference.

Check warning on line 1983 in src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs

View workflow job for this annotation

GitHub Actions / Linux: Build and Tests

Dereference of a possibly null reference.
propertyOrFieldExpression = propertyIsStatic ? Expression.Property(null, property) : Expression.Property(expression, property);
return true;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -517,12 +517,14 @@ public void Where_With_Select()
[InlineData("notExisting == \"1\"")]
[InlineData("notExisting == \"something\"")]
[InlineData("notExisting > 1")]
[InlineData("notExisting < 1")]
[InlineData("true == notExisting")]
[InlineData("\"true\" == notExisting")]
[InlineData("1 == notExisting")]
[InlineData("\"1\" == notExisting")]
[InlineData("\"something\" == notExisting")]
[InlineData("1 < notExisting")]
[InlineData("1 > notExisting")]
public void Where_NonExistingMember_EmptyResult(string predicate)
{
// Arrange
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -546,12 +546,14 @@ public void Where_With_Select()
[InlineData("notExisting == \"1\"")]
[InlineData("notExisting == \"something\"")]
[InlineData("notExisting > 1")]
[InlineData("notExisting < 1")]
[InlineData("true == notExisting")]
[InlineData("\"true\" == notExisting")]
[InlineData("1 == notExisting")]
[InlineData("\"1\" == notExisting")]
[InlineData("\"something\" == notExisting")]
[InlineData("1 < notExisting")]
[InlineData("1 > notExisting")]
public void Where_NonExistingMember_EmptyResult(string predicate)
{
// Act
Expand Down
Loading