using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Runtime.ExceptionServices;
using DynamicExpresso.Reflection;
using DynamicExpresso.Resources;
namespace DynamicExpresso
{
///
/// Represents a lambda expression that can be invoked. This class is thread safe.
///
public class Lambda
{
private readonly Expression _expression;
private readonly ParserArguments _parserArguments;
private readonly Lazy _delegate;
internal Lambda(Expression expression, ParserArguments parserArguments)
{
_expression = expression ?? throw new ArgumentNullException(nameof(expression));
_parserArguments = parserArguments ?? throw new ArgumentNullException(nameof(parserArguments));
// Note: I always lazy compile the generic lambda. Maybe in the future this can be a setting because if I generate a typed delegate this compilation is not required.
_delegate = new Lazy(() =>
Expression.Lambda(_expression, _parserArguments.UsedParameters.Select(p => p.Expression).ToArray()).Compile());
}
public Expression Expression { get { return _expression; } }
public bool CaseInsensitive { get { return _parserArguments.Settings.CaseInsensitive; } }
public string ExpressionText { get { return _parserArguments.ExpressionText; } }
public Type ReturnType { get { return Expression.Type; } }
///
/// Gets the parameters actually used in the expression parsed.
///
/// The used parameters.
[Obsolete("Use UsedParameters or DeclaredParameters")]
public IEnumerable Parameters { get { return _parserArguments.UsedParameters; } }
///
/// Gets the parameters actually used in the expression parsed.
///
/// The used parameters.
public IEnumerable UsedParameters { get { return _parserArguments.UsedParameters; } }
///
/// Gets the parameters declared when parsing the expression.
///
/// The declared parameters.
public IEnumerable DeclaredParameters { get { return _parserArguments.DeclaredParameters; } }
public IEnumerable Types { get { return _parserArguments.UsedTypes; } }
public IEnumerable Identifiers { get { return _parserArguments.UsedIdentifiers; } }
public object Invoke()
{
return InvokeWithUsedParameters(new object[0]);
}
public object Invoke(params Parameter[] parameters)
{
return Invoke((IEnumerable)parameters);
}
public object Invoke(IEnumerable parameters)
{
var args = (from usedParameter in UsedParameters
from actualParameter in parameters
where usedParameter.Name.Equals(actualParameter.Name, _parserArguments.Settings.KeyComparison)
select actualParameter.Value)
.ToArray();
return InvokeWithUsedParameters(args);
}
///
/// Invoke the expression with the given parameters values.
///
/// Order of parameters must be the same of the parameters used during parse (DeclaredParameters).
///
public object Invoke(params object[] args)
{
var parameters = new List();
var declaredParameters = DeclaredParameters.ToArray();
if (args != null)
{
if (declaredParameters.Length != args.Length)
throw new InvalidOperationException(ErrorMessages.ArgumentCountMismatch);
for (var i = 0; i < args.Length; i++)
{
var parameter = new Parameter(
declaredParameters[i].Name,
declaredParameters[i].Type,
args[i]);
parameters.Add(parameter);
}
}
return Invoke(parameters);
}
private object InvokeWithUsedParameters(object[] orderedArgs)
{
try
{
return _delegate.Value.DynamicInvoke(orderedArgs);
}
catch (TargetInvocationException exc)
{
if (exc.InnerException != null)
ExceptionDispatchInfo.Capture(exc.InnerException).Throw();
throw;
}
}
public override string ToString()
{
return ExpressionText;
}
///
/// Generate the given delegate by compiling the lambda expression.
///
/// The delegate to generate. Delegate parameters must match the one defined when creating the expression, see UsedParameters.
public TDelegate Compile()
{
var lambdaExpression = LambdaExpression();
return lambdaExpression.Compile();
}
[Obsolete("Use Compile()")]
public TDelegate Compile(IEnumerable parameters)
{
var lambdaExpression = Expression.Lambda(_expression, parameters.Select(p => p.Expression).ToArray());
return lambdaExpression.Compile();
}
///
/// Generate a lambda expression.
///
/// The lambda expression.
/// The delegate to generate. Delegate parameters must match the one defined when creating the expression, see UsedParameters.
public Expression LambdaExpression()
{
return Expression.Lambda(_expression, DeclaredParameters.Select(p => p.Expression).ToArray());
}
internal LambdaExpression LambdaExpression(Type delegateType)
{
var parameterExpressions = DeclaredParameters.Select(p => p.Expression).ToArray();
var types = delegateType.GetGenericArguments();
// return type
if (delegateType.GetGenericTypeDefinition() == ReflectionExtensions.GetFuncType(parameterExpressions.Length))
types[types.Length - 1] = _expression.Type;
var genericType = delegateType.GetGenericTypeDefinition();
var inferredDelegateType = genericType.MakeGenericType(types);
return Expression.Lambda(inferredDelegateType, _expression, parameterExpressions);
}
}
}