diff --git a/Bonsai.Core/Expressions/TryGetValueBuilder.cs b/Bonsai.Core/Expressions/TryGetValueBuilder.cs new file mode 100644 index 000000000..c76ab4511 --- /dev/null +++ b/Bonsai.Core/Expressions/TryGetValueBuilder.cs @@ -0,0 +1,116 @@ +using System; +using System.ComponentModel; +using System.Linq.Expressions; +using System.Reflection; +using System.Xml.Serialization; +using Bonsai.Properties; + +namespace Bonsai.Expressions +{ + /// + /// Represents an expression builder for an operator that determines the existence of a specific key, + /// and returns its value, if it exists. + /// + [XmlType("TryGetValue", Namespace = Constants.XmlNamespace)] + [Description("Applies an operator to an observable sequence, that determines the existence of a specific key, and returns its value, if it exists.")] + public class TryGetValueBuilder : BinaryOperatorBuilder + { + /// + /// Returns the expression that maps the specified input parameter to the selector result. + /// + /// The input parameter to the selector. + /// + /// The that maps the input parameter to the + /// selector result. + /// + protected override Expression BuildSelector(Expression expression) + { + Expression left, right; + var expressionTypeDefinition = expression.Type.IsGenericType ? expression.Type.GetGenericTypeDefinition() : null; + if (expressionTypeDefinition == typeof(Tuple<,>)) + { + Operand = null; + left = ExpressionHelper.MemberAccess(expression, "Item1"); + right = ExpressionHelper.MemberAccess(expression, "Item2"); + return BuildSelector(left, right); + } + else + { + var operand = Operand; + var operandType = ExpressionHelper.GetIndexerTypes(expression, 1)[0]; + if (operand == null || operand.PropertyType != operandType) + { + var propertyType = GetWorkflowPropertyType(operandType); + try + { + Operand = operand = (WorkflowProperty)Activator.CreateInstance(propertyType); + } + catch (Exception ex) + { + throw new InvalidOperationException( + string.Format(Resources.Exception_UnsupportedMinArgumentCount, 2), + ex); + } + } + + left = expression; + var operandExpression = Expression.Constant(operand); + right = ExpressionHelper.Property(operandExpression, "Value"); + } + + return BuildSelector(left, right); + } + + /// + /// Returns the expression that applies the operator to + /// the left and right parameters. + /// + /// The left input parameter. + /// The right input parameter. + /// + /// The that applies a TryGetValue operation to + /// the left and right parameters. + /// + protected override Expression BuildSelector(Expression left, Expression right) + { + // We could grab the TryGetValue method directly, but we need the indexer type anyway + PropertyInfo indexer = left.Type.GetProperty("Item", new[] { right.Type }); + + if (indexer == null) + { + throw new Exception( + $"Type {left.Type.Name} does not support indexing with {right.Type.Name}" + ); + } + + var valueType = indexer.PropertyType; + + MethodInfo tryGetValueMethod = left.Type.GetMethod( + "TryGetValue", + new[] { right.Type, valueType.MakeByRefType() } + ); + + if (tryGetValueMethod == null) + { + throw new Exception( + $"Type {left.Type.Name} does not support TryGetValue({right.Type.Name}, out {valueType.Name})" + ); + } + + var outputTupleType = typeof(Tuple<,>).MakeGenericType(typeof(bool), valueType); + var outputConstructor = outputTupleType.GetConstructor(new[] { typeof(bool), valueType }); + + var valueVariable = Expression.Variable(valueType, "value"); + + return Expression.Block( + outputTupleType, + new[] { valueVariable }, + Expression.New( + outputConstructor, + Expression.Call(left, tryGetValueMethod, right, valueVariable), + valueVariable + ) + ); + } + } +}