1 /* ****************************************************************************
3 * Copyright (c) Microsoft Corporation.
5 * This source code is subject to terms and conditions of the Microsoft Public License. A
6 * copy of the license can be found in the License.html file at the root of this distribution. If
7 * you cannot locate the Microsoft Public License, please send an email to
8 * dlr@microsoft.com. By using this source code in any fashion, you are agreeing to be bound
9 * by the terms of the Microsoft Public License.
11 * You must not remove this notice, or any other, from this software.
14 * ***************************************************************************/
15 using System; using Microsoft;
18 using System.Collections.Generic;
19 using System.Collections.ObjectModel;
20 using System.Diagnostics;
22 using System.Dynamic.Utils;
24 using Microsoft.Scripting.Utils;
26 using System.Reflection;
27 using System.Runtime.CompilerServices;
29 using Microsoft.Runtime.CompilerServices;
35 namespace System.Linq.Expressions {
37 namespace Microsoft.Linq.Expressions {
40 /// Represents indexing a property or array.
43 [DebuggerTypeProxy(typeof(Expression.IndexExpressionProxy))]
45 public sealed class IndexExpression : Expression, IArgumentProvider {
46 private readonly Expression _instance;
47 private readonly PropertyInfo _indexer;
48 private IList<Expression> _arguments;
50 internal IndexExpression(
53 IList<Expression> arguments) {
55 if (indexer == null) {
56 Debug.Assert(instance != null && instance.Type.IsArray);
57 Debug.Assert(instance.Type.GetArrayRank() == arguments.Count);
62 _arguments = arguments;
66 /// Returns the node type of this <see cref="Expression" />. (Inherited from <see cref="Expression" />.)
68 /// <returns>The <see cref="ExpressionType"/> that represents this expression.</returns>
69 public sealed override ExpressionType NodeType {
70 get { return ExpressionType.Index; }
74 /// Gets the static type of the expression that this <see cref="Expression" /> represents. (Inherited from <see cref="Expression"/>.)
76 /// <returns>The <see cref="Type"/> that represents the static type of the expression.</returns>
77 public sealed override Type Type {
79 if (_indexer != null) {
80 return _indexer.PropertyType;
82 return _instance.Type.GetElementType();
87 /// An object to index.
89 public Expression Object {
90 get { return _instance; }
94 /// Gets the <see cref="PropertyInfo"/> for the property if the expression represents an indexed property, returns null otherwise.
96 public PropertyInfo Indexer {
97 get { return _indexer; }
101 /// Gets the arguments to be used to index the property or array.
103 public ReadOnlyCollection<Expression> Arguments {
104 get { return ReturnReadOnly(ref _arguments); }
107 Expression IArgumentProvider.GetArgument(int index) {
108 return _arguments[index];
111 int IArgumentProvider.ArgumentCount {
113 return _arguments.Count;
117 internal override Expression Accept(ExpressionVisitor visitor) {
118 return visitor.VisitIndex(this);
121 internal Expression Rewrite(Expression instance, Expression[] arguments) {
122 Debug.Assert(instance != null);
123 Debug.Assert(arguments == null || arguments.Length == _arguments.Count);
125 return Expression.MakeIndex(instance, _indexer, arguments ?? _arguments);
129 public partial class Expression {
132 /// Creates an <see cref="IndexExpression"/> that represents accessing an indexed property in an object.
134 /// <param name="instance">The object to which the property belongs. Should be null if the property is static(shared).</param>
135 /// <param name="indexer">An <see cref="Expression"/> representing the property to index.</param>
136 /// <param name="arguments">An IEnumerable{Expression} contaning the arguments to be used to index the property.</param>
137 /// <returns>The created <see cref="IndexExpression"/>.</returns>
138 public static IndexExpression MakeIndex(Expression instance, PropertyInfo indexer, IEnumerable<Expression> arguments) {
139 if (indexer != null) {
140 return Property(instance, indexer, arguments);
142 return ArrayAccess(instance, arguments);
149 /// Creates an <see cref="IndexExpression"></see> to access an array.
151 /// <param name="array">An expression representing the array to index.</param>
152 /// <param name="indexes">An array containing expressions used to index the array.</param>
153 /// <remarks>The expression representing the array can be obtained by using the MakeMemberAccess method,
154 /// or through NewArrayBounds or NewArrayInit.</remarks>
155 /// <returns>The created <see cref="IndexExpression"/>.</returns>
156 public static IndexExpression ArrayAccess(Expression array, params Expression[] indexes) {
157 return ArrayAccess(array, (IEnumerable<Expression>)indexes);
161 /// Creates an <see cref="IndexExpression"></see> to access an array.
163 /// <param name="array">An expression representing the array to index.</param>
164 /// <param name="indexes">An <see cref="IEnumerable{Expression}"/> containing expressions used to index the array.</param>
165 /// <remarks>The expression representing the array can be obtained by using the MakeMemberAccess method,
166 /// or through NewArrayBounds or NewArrayInit.</remarks>
167 /// <returns>The created <see cref="IndexExpression"/>.</returns>
168 public static IndexExpression ArrayAccess(Expression array, IEnumerable<Expression> indexes) {
169 RequiresCanRead(array, "array");
171 Type arrayType = array.Type;
172 if (!arrayType.IsArray) {
173 throw Error.ArgumentMustBeArray();
176 var indexList = indexes.ToReadOnly();
177 if (arrayType.GetArrayRank() != indexList.Count) {
178 throw Error.IncorrectNumberOfIndexes();
181 foreach (Expression e in indexList) {
182 RequiresCanRead(e, "indexes");
183 if (e.Type != typeof(int)) {
184 throw Error.ArgumentMustBeArrayIndexType();
188 return new IndexExpression(array, null, indexList);
195 /// Creates an <see cref="IndexExpression"/> representing the access to an indexed property.
197 /// <param name="instance">The object to which the property belongs. If the property is static/shared, it must be null.</param>
198 /// <param name="propertyName">The name of the indexer.</param>
199 /// <param name="arguments">An array of <see cref="Expression"/> objects that are used to index the property.</param>
200 /// <returns>The created <see cref="IndexExpression"/>.</returns>
201 public static IndexExpression Property(Expression instance, string propertyName, params Expression[] arguments) {
202 RequiresCanRead(instance, "instance");
203 ContractUtils.RequiresNotNull(propertyName, "indexerName");
204 PropertyInfo pi = FindInstanceProperty(instance.Type, propertyName, arguments);
205 return Property(instance, pi, arguments);
208 #region methods for finding a PropertyInfo by its name
210 /// The method finds the instance property with the specified name in a type. The property's type signature needs to be compatible with
211 /// the arguments if it is a indexer. If the arguments is null or empty, we get a normal property.
213 private static PropertyInfo FindInstanceProperty(Type type, string propertyName, Expression[] arguments) {
214 // bind to public names first
215 BindingFlags flags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.IgnoreCase | BindingFlags.FlattenHierarchy;
216 PropertyInfo pi = FindProperty(type, propertyName, arguments, flags);
218 flags = BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.IgnoreCase | BindingFlags.FlattenHierarchy;
219 pi = FindProperty(type, propertyName, arguments, flags);
222 if (arguments == null || arguments.Length == 0) {
223 throw Error.InstancePropertyWithoutParameterNotDefinedForType(propertyName, type);
225 throw Error.InstancePropertyWithSpecifiedParametersNotDefinedForType(propertyName, GetArgTypesString(arguments), type);
231 private static string GetArgTypesString(Expression[] arguments) {
232 StringBuilder argTypesStr = new StringBuilder();
234 argTypesStr.Append("(");
235 foreach (var t in arguments.Select(arg => arg.Type)) {
237 argTypesStr.Append(", ");
239 argTypesStr.Append(t.Name);
242 argTypesStr.Append(")");
243 return argTypesStr.ToString();
246 private static PropertyInfo FindProperty(Type type, string propertyName, Expression[] arguments, BindingFlags flags) {
247 MemberInfo[] members = type.FindMembers(MemberTypes.Property, flags, Type.FilterNameIgnoreCase, propertyName);
248 if (members == null || members.Length == 0)
252 var propertyInfos = members.Map(t => (PropertyInfo)t);
253 int count = FindBestProperty(propertyInfos, arguments, out pi);
258 throw Error.PropertyWithMoreThanOneMatch(propertyName, type);
262 private static int FindBestProperty(IEnumerable<PropertyInfo> properties, Expression[] args, out PropertyInfo property) {
265 foreach (PropertyInfo pi in properties) {
266 if (pi != null && IsCompatible(pi, args)) {
267 if (property == null) {
279 private static bool IsCompatible(PropertyInfo pi, Expression[] args) {
282 mi = pi.GetGetMethod(true);
283 ParameterInfo[] parms;
285 parms = mi.GetParametersCached();
287 mi = pi.GetSetMethod(true);
288 //The setter has an additional parameter for the value to set,
289 //need to remove the last type to match the arguments.
290 parms = mi.GetParametersCached().RemoveLast();
297 return parms.Length == 0;
300 if (parms.Length != args.Length)
302 for (int i = 0; i < args.Length; i++) {
303 if (args[i] == null) return false;
304 if (!TypeUtils.AreReferenceAssignable(parms[i].ParameterType, args[i].Type)) {
313 /// Creates an <see cref="IndexExpression"/> representing the access to an indexed property.
315 /// <param name="instance">The object to which the property belongs. If the property is static/shared, it must be null.</param>
316 /// <param name="indexer">The <see cref="PropertyInfo"/> that represents the property to index.</param>
317 /// <param name="arguments">An array of <see cref="Expression"/> objects that are used to index the property.</param>
318 /// <returns>The created <see cref="IndexExpression"/>.</returns>
319 public static IndexExpression Property(Expression instance, PropertyInfo indexer, params Expression[] arguments) {
320 return Property(instance, indexer, (IEnumerable<Expression>)arguments);
324 /// Creates an <see cref="IndexExpression"/> representing the access to an indexed property.
326 /// <param name="instance">The object to which the property belongs. If the property is static/shared, it must be null.</param>
327 /// <param name="indexer">The <see cref="PropertyInfo"/> that represents the property to index.</param>
328 /// <param name="arguments">An <see cref="IEnumerable{T}"/> of <see cref="Expression"/> objects that are used to index the property.</param>
329 /// <returns>The created <see cref="IndexExpression"/>.</returns>
330 public static IndexExpression Property(Expression instance, PropertyInfo indexer, IEnumerable<Expression> arguments) {
331 var argList = arguments.ToReadOnly();
332 ValidateIndexedProperty(instance, indexer, ref argList);
333 return new IndexExpression(instance, indexer, argList);
336 // CTS places no restrictions on properties (see ECMA-335 8.11.3),
337 // so we validate that the property conforms to CLS rules here.
339 // Does reflection help us out at all? Expression.Property skips all of
340 // these checks, so either it needs more checks or we need less here.
341 private static void ValidateIndexedProperty(Expression instance, PropertyInfo property, ref ReadOnlyCollection<Expression> argList) {
343 // If both getter and setter specified, all their parameter types
344 // should match, with exception of the last setter parameter which
345 // should match the type returned by the get method.
346 // Accessor parameters cannot be ByRef.
348 ContractUtils.RequiresNotNull(property, "property");
349 ContractUtils.Requires(!property.PropertyType.IsByRef, "property", Strings.PropertyCannotHaveRefType);
350 ContractUtils.Requires(property.PropertyType != typeof(void), "property", Strings.PropertyTypeCannotBeVoid);
352 ParameterInfo[] getParameters = null;
353 MethodInfo getter = property.GetGetMethod(true);
354 if (getter != null) {
355 getParameters = getter.GetParametersCached();
356 ValidateAccessor(instance, getter, getParameters, ref argList);
359 MethodInfo setter = property.GetSetMethod(true);
360 if (setter != null) {
361 ParameterInfo[] setParameters = setter.GetParametersCached();
362 ContractUtils.Requires(setParameters.Length > 0, "property", Strings.SetterHasNoParams);
364 // valueType is the type of the value passed to the setter (last parameter)
365 Type valueType = setParameters[setParameters.Length - 1].ParameterType;
366 ContractUtils.Requires(!valueType.IsByRef, "property", Strings.PropertyCannotHaveRefType);
367 ContractUtils.Requires(setter.ReturnType == typeof(void), "property", Strings.SetterMustBeVoid);
368 ContractUtils.Requires(property.PropertyType == valueType, "property", Strings.PropertyTyepMustMatchSetter);
370 if (getter != null) {
371 ContractUtils.Requires(!(getter.IsStatic ^ setter.IsStatic), "property", Strings.BothAccessorsMustBeStatic);
372 ContractUtils.Requires(getParameters.Length == setParameters.Length - 1, "property", Strings.IndexesOfSetGetMustMatch);
374 for (int i = 0; i < getParameters.Length; i++) {
375 ContractUtils.Requires(getParameters[i].ParameterType == setParameters[i].ParameterType, "property", Strings.IndexesOfSetGetMustMatch);
378 ValidateAccessor(instance, setter, setParameters.RemoveLast(), ref argList);
382 if (getter == null && setter == null) {
383 throw Error.PropertyDoesNotHaveAccessor(property);
387 private static void ValidateAccessor(Expression instance, MethodInfo method, ParameterInfo[] indexes, ref ReadOnlyCollection<Expression> arguments) {
388 ContractUtils.RequiresNotNull(arguments, "arguments");
390 ValidateMethodInfo(method);
391 ContractUtils.Requires((method.CallingConvention & CallingConventions.VarArgs) == 0, "method", Strings.AccessorsCannotHaveVarArgs);
392 if (method.IsStatic) {
393 ContractUtils.Requires(instance == null, "instance", Strings.OnlyStaticMethodsHaveNullInstance);
395 ContractUtils.Requires(instance != null, "method", Strings.OnlyStaticMethodsHaveNullInstance);
396 RequiresCanRead(instance, "instance");
397 ValidateCallInstanceType(instance.Type, method);
400 ValidateAccessorArgumentTypes(method, indexes, ref arguments);
403 private static void ValidateAccessorArgumentTypes(MethodInfo method, ParameterInfo[] indexes, ref ReadOnlyCollection<Expression> arguments) {
404 if (indexes.Length > 0) {
405 if (indexes.Length != arguments.Count) {
406 throw Error.IncorrectNumberOfMethodCallArguments(method);
408 Expression[] newArgs = null;
409 for (int i = 0, n = indexes.Length; i < n; i++) {
410 Expression arg = arguments[i];
411 ParameterInfo pi = indexes[i];
412 RequiresCanRead(arg, "arguments");
414 Type pType = pi.ParameterType;
415 ContractUtils.Requires(!pType.IsByRef, "indexes", Strings.AccessorsCannotHaveByRefArgs);
416 TypeUtils.ValidateType(pType);
418 if (!TypeUtils.AreReferenceAssignable(pType, arg.Type)) {
419 if (TypeUtils.IsSameOrSubclass(typeof(LambdaExpression), pType) && pType.IsAssignableFrom(arg.GetType())) {
420 arg = Expression.Quote(arg);
422 throw Error.ExpressionTypeDoesNotMatchMethodParameter(arg.Type, pType, method);
425 if (newArgs == null && arg != arguments[i]) {
426 newArgs = new Expression[arguments.Count];
427 for (int j = 0; j < i; j++) {
428 newArgs[j] = arguments[j];
431 if (newArgs != null) {
435 if (newArgs != null) {
436 arguments = new TrueReadOnlyCollection<Expression>(newArgs);
439 } else if (arguments.Count > 0) {
440 throw Error.IncorrectNumberOfMethodCallArguments(method);