#if NET20 || NET30 || !NET_4_6 // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics; using System.Dynamic.Utils; using System.Reflection; using System.Runtime.CompilerServices; using System.Text; using LinqInternal.Collections; using LinqInternal.Core; namespace System.Linq.Expressions.Reimplement { /// /// Represents indexing a property or array. /// [DebuggerTypeProxy(typeof(IndexExpressionProxy))] public sealed class IndexExpression : Expression, IArgumentProvider { private readonly Expression _instance; private readonly PropertyInfo _indexer; private IList _arguments; internal IndexExpression( Expression instance, PropertyInfo indexer, IList arguments) { if (indexer == null) { Debug.Assert(instance != null && instance.Type.IsArray); Debug.Assert(instance.Type.GetArrayRank() == arguments.Count); } _instance = instance; _indexer = indexer; _arguments = arguments; } /// /// Returns the node type of this . (Inherited from .) /// /// The that represents this expression. public override ExpressionType NodeType { get { return ExpressionType.Index; } } /// /// Gets the static type of the expression that this represents. (Inherited from .) /// /// The that represents the static type of the expression. public override Type Type { get { if (_indexer != null) { return _indexer.PropertyType; } return _instance.Type.GetElementType(); } } /// /// An object to index. /// public Expression Object { get { return _instance; } } /// /// Gets the for the property if the expression represents an indexed property, returns null otherwise. /// public PropertyInfo Indexer { get { return _indexer; } } /// /// Gets the arguments to be used to index the property or array. /// public ReadOnlyCollection Arguments { get { return ReturnReadOnly(ref _arguments); } } /// /// Creates a new expression that is like this one, but using the /// supplied children. If all of the children are the same, it will /// return this expression. /// /// The property of the result. /// The property of the result. /// This expression if no children changed, or an expression with the updated children. public IndexExpression Update(Expression @object, IEnumerable arguments) { if (@object == Object && arguments == Arguments) { return this; } return MakeIndex(@object, Indexer, arguments); } public Expression GetArgument(int index) { return _arguments[index]; } public int ArgumentCount { get { return _arguments.Count; } } protected internal override Expression Accept(ExpressionVisitor visitor) { return visitor.VisitIndex(this); } internal Expression Rewrite(Expression instance, Expression[] arguments) { Debug.Assert(instance != null); Debug.Assert(arguments == null || arguments.Length == _arguments.Count); return MakeIndex(instance, _indexer, arguments ?? _arguments); } } public partial class Expression { /// /// Creates an that represents accessing an indexed property in an object. /// /// The object to which the property belongs. Should be null if the property is static(shared). /// An representing the property to index. /// An IEnumerable{Expression} containing the arguments to be used to index the property. /// The created . public static IndexExpression MakeIndex(Expression instance, PropertyInfo indexer, IEnumerable arguments) { if (indexer != null) { return Property(instance, indexer, arguments); } else { return ArrayAccess(instance, arguments); } } #region ArrayAccess /// /// Creates an to access an array. /// /// An expression representing the array to index. /// An array containing expressions used to index the array. /// The expression representing the array can be obtained by using the MakeMemberAccess method, /// or through NewArrayBounds or NewArrayInit. /// The created . public static IndexExpression ArrayAccess(Expression array, params Expression[] indexes) { return ArrayAccess(array, (IEnumerable)indexes); } /// /// Creates an to access an array. /// /// An expression representing the array to index. /// An containing expressions used to index the array. /// The expression representing the array can be obtained by using the MakeMemberAccess method, /// or through NewArrayBounds or NewArrayInit. /// The created . public static IndexExpression ArrayAccess(Expression array, IEnumerable indexes) { RequiresCanRead(array, "array"); var arrayType = array.Type; if (!arrayType.IsArray) { throw Error.ArgumentMustBeArray(); } var indexList = indexes.ToReadOnly(); if (arrayType.GetArrayRank() != indexList.Count) { throw Error.IncorrectNumberOfIndexes(); } foreach (var e in indexList) { RequiresCanRead(e, "indexes"); if (e.Type != typeof(int)) { throw Error.ArgumentMustBeArrayIndexType(); } } return new IndexExpression(array, null, indexList); } #endregion ArrayAccess #region Property /// /// Creates an representing the access to an indexed property. /// /// The object to which the property belongs. If the property is static/shared, it must be null. /// The name of the indexer. /// An array of objects that are used to index the property. /// The created . public static IndexExpression Property(Expression instance, string propertyName, params Expression[] arguments) { RequiresCanRead(instance, "instance"); ContractUtils.RequiresNotNull(propertyName, "indexerName"); var pi = FindInstanceProperty(instance.Type, propertyName, arguments); return Property(instance, pi, arguments); } #region methods for finding a PropertyInfo by its name private static PropertyInfo FindInstanceProperty(Type type, string propertyName, Expression[] arguments) { // bind to public names first var flags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.IgnoreCase | BindingFlags.FlattenHierarchy; var pi = FindProperty(type, propertyName, arguments, flags); if (pi == null) { flags = BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.IgnoreCase | BindingFlags.FlattenHierarchy; pi = FindProperty(type, propertyName, arguments, flags); } if (pi == null) { if (arguments == null || arguments.Length == 0) { throw Error.InstancePropertyWithoutParameterNotDefinedForType(propertyName, type); } else { throw Error.InstancePropertyWithSpecifiedParametersNotDefinedForType(propertyName, GetArgTypesString(arguments), type); } } return pi; } private static string GetArgTypesString(Expression[] arguments) { var argTypesStr = new StringBuilder(); argTypesStr.Append('('); for (var i = 0; i < arguments.Length; i++) { if (i != 0) { argTypesStr.Append(", "); } argTypesStr.Append(arguments[i].Type.Name); } argTypesStr.Append(')'); return argTypesStr.ToString(); } private static PropertyInfo FindProperty(Type type, string propertyName, Expression[] arguments, BindingFlags flags) { var props = type.GetProperties(flags).Where(x => x.Name.Equals(propertyName, StringComparison.CurrentCultureIgnoreCase)); var members = new List(props).ToArray(); if (members == null || members.Length == 0) { return null; } PropertyInfo pi; var propertyInfos = members.Map(t => t); var count = FindBestProperty(propertyInfos, arguments, out pi); if (count == 0) { return null; } if (count > 1) { throw Error.PropertyWithMoreThanOneMatch(propertyName, type); } return pi; } private static int FindBestProperty(IEnumerable properties, Expression[] args, out PropertyInfo property) { var count = 0; property = null; foreach (var pi in properties) { if (pi != null && IsCompatible(pi, args)) { if (property == null) { property = pi; count = 1; } else { count++; } } } return count; } private static bool IsCompatible(PropertyInfo pi, Expression[] args) { var mi = pi.GetGetMethod(true); ParameterInfo[] parms; if (mi != null) { parms = mi.GetParameters(); } else { mi = pi.GetSetMethod(true); //The setter has an additional parameter for the value to set, //need to remove the last type to match the arguments. parms = mi.GetParameters().RemoveLast(); } if (mi == null) { return false; } if (args == null) { return parms.Length == 0; } if (parms.Length != args.Length) { return false; } for (var i = 0; i < args.Length; i++) { if (args[i] == null) { return false; } if (!TypeHelper.AreReferenceAssignable(parms[i].ParameterType, args[i].Type)) { return false; } } return true; } #endregion methods for finding a PropertyInfo by its name /// /// Creates an representing the access to an indexed property. /// /// The object to which the property belongs. If the property is static/shared, it must be null. /// The that represents the property to index. /// An array of objects that are used to index the property. /// The created . public static IndexExpression Property(Expression instance, PropertyInfo indexer, params Expression[] arguments) { return Property(instance, indexer, (IEnumerable)arguments); } /// /// Creates an representing the access to an indexed property. /// /// The object to which the property belongs. If the property is static/shared, it must be null. /// The that represents the property to index. /// An of objects that are used to index the property. /// The created . public static IndexExpression Property(Expression instance, PropertyInfo indexer, IEnumerable arguments) { var argList = arguments.ToReadOnly(); ValidateIndexedProperty(instance, indexer, ref argList); return new IndexExpression(instance, indexer, argList); } // CTS places no restrictions on properties (see ECMA-335 8.11.3), // so we validate that the property conforms to CLS rules here. // // Does reflection help us out at all? Expression.Property skips all of // these checks, so either it needs more checks or we need less here. private static void ValidateIndexedProperty(Expression instance, PropertyInfo property, ref ReadOnlyCollection argList) { // If both getter and setter specified, all their parameter types // should match, with exception of the last setter parameter which // should match the type returned by the get method. // Accessor parameters cannot be ByRef. ContractUtils.RequiresNotNull(property, "property"); if (property.PropertyType.IsByRef) { throw Error.PropertyCannotHaveRefType(); } if (property.PropertyType == typeof(void)) { throw Error.PropertyTypeCannotBeVoid(); } ParameterInfo[] getParameters = null; var getter = property.GetGetMethod(true); if (getter != null) { getParameters = getter.GetParameters(); ValidateAccessor(instance, getter, getParameters, ref argList); } var setter = property.GetSetMethod(true); if (setter != null) { var setParameters = setter.GetParameters(); if (setParameters.Length == 0) { throw Error.SetterHasNoParams(); } // valueType is the type of the value passed to the setter (last parameter) var valueType = setParameters[setParameters.Length - 1].ParameterType; if (valueType.IsByRef) { throw Error.PropertyCannotHaveRefType(); } if (setter.ReturnType != typeof(void)) { throw Error.SetterMustBeVoid(); } if (property.PropertyType != valueType) { throw Error.PropertyTyepMustMatchSetter(); } if (getter != null) { if (getter.IsStatic ^ setter.IsStatic) { throw Error.BothAccessorsMustBeStatic(); } if (getParameters.Length != setParameters.Length - 1) { throw Error.IndexesOfSetGetMustMatch(); } for (var i = 0; i < getParameters.Length; i++) { if (getParameters[i].ParameterType != setParameters[i].ParameterType) { throw Error.IndexesOfSetGetMustMatch(); } } } else { ValidateAccessor(instance, setter, setParameters.RemoveLast(), ref argList); } } if (getter == null && setter == null) { throw Error.PropertyDoesNotHaveAccessor(property); } } private static void ValidateAccessor(Expression instance, MethodInfo method, ParameterInfo[] indexes, ref ReadOnlyCollection arguments) { ContractUtils.RequiresNotNull(arguments, "arguments"); ValidateMethodInfo(method); if ((method.CallingConvention & CallingConventions.VarArgs) != 0) { throw Error.AccessorsCannotHaveVarArgs(); } if (method.IsStatic) { if (instance != null) { throw Error.OnlyStaticMethodsHaveNullInstance(); } } else { if (instance == null) { throw Error.OnlyStaticMethodsHaveNullInstance(); } RequiresCanRead(instance, "instance"); ValidateCallInstanceType(instance.Type, method); } ValidateAccessorArgumentTypes(method, indexes, ref arguments); } private static void ValidateAccessorArgumentTypes(MethodInfo method, ParameterInfo[] indexes, ref ReadOnlyCollection arguments) { if (indexes.Length > 0) { if (indexes.Length != arguments.Count) { throw Error.IncorrectNumberOfMethodCallArguments(method); } Expression[] newArgs = null; var n = indexes.Length; for (var i = 0; i < n; i++) { var arg = arguments[i]; var pi = indexes[i]; RequiresCanRead(arg, "arguments"); var pType = pi.ParameterType; if (pType.IsByRef) { throw Error.AccessorsCannotHaveByRefArgs(); } TypeHelper.ValidateType(pType); if (!TypeHelper.AreReferenceAssignable(pType, arg.Type)) { if (!TryQuote(pType, ref arg)) { throw Error.ExpressionTypeDoesNotMatchMethodParameter(arg.Type, pType, method); } } if (newArgs == null && arg != arguments[i]) { newArgs = new Expression[arguments.Count]; for (var j = 0; j < i; j++) { newArgs[j] = arguments[j]; } } if (newArgs != null) { newArgs[i] = arg; } } if (newArgs != null) { arguments = new TrueReadOnlyCollection(newArgs); } } else if (arguments.Count > 0) { throw Error.IncorrectNumberOfMethodCallArguments(method); } } #endregion Property } } #endif