/// <summary> /// Creates an expression representing an indexer access operation. /// </summary> /// <param name="object">The object to access.</param> /// <param name="argument">The argument that will be used to index or slice the object.</param> /// <param name="lengthOrCount">The property used to retrieve the element count of the object getting accessed.</param> /// <param name="indexOrSlice">The member used to index or slice the object.</param> /// <returns>A new <see cref="IndexerAccessCSharpExpression"/> instance representing the array access operation.</returns> public static IndexerAccessCSharpExpression IndexerAccess(Expression @object, Expression argument, PropertyInfo lengthOrCount, MemberInfo indexOrSlice) { RequiresCanRead(@object, nameof(@object)); // // The argument can be of type Index or Range. We'll check indexOrSlice accordingly below. // RequiresCanRead(argument, nameof(argument)); if (argument.Type != typeof(Index) && argument.Type != typeof(Range)) { throw Error.InvalidIndexerAccessArgumentType(argument.Type); } // // A type is Countable if it has a property named Length or Count with an accessible getter and a return type of int. // lengthOrCount ??= FindCountProperty("Length") ?? FindCountProperty("Count"); PropertyInfo FindCountProperty(string name) => @object.Type.GetProperty(name, BindingFlags.Public | BindingFlags.Instance, binder: null, typeof(int), Type.EmptyTypes, modifiers: null); ContractUtils.RequiresNotNull(lengthOrCount, nameof(lengthOrCount)); var lengthOrCountGetMethod = lengthOrCount.GetGetMethod(nonPublic: true); // NB: System.Linq.Expressions allows non-public properties. if (lengthOrCountGetMethod == null) { throw LinqError.PropertyDoesNotHaveAccessor(lengthOrCount); } if (lengthOrCountGetMethod.IsStatic) { throw Error.AccessorCannotBeStatic(lengthOrCountGetMethod); } if (lengthOrCountGetMethod.GetParametersCached().Length != 0) { throw LinqError.IncorrectNumberOfMethodCallArguments(lengthOrCountGetMethod); } if (!TypeUtils.IsValidInstanceType(lengthOrCount, @object.Type)) { throw LinqError.PropertyNotDefinedForType(lengthOrCount, @object.Type); } if (lengthOrCount.PropertyType != typeof(int)) { throw Error.InvalidLengthOrCountPropertyType(lengthOrCount); } ValidateMethodInfo(lengthOrCountGetMethod); if (argument.Type == typeof(Index)) { // // The type has an accessible instance indexer which takes a single int as the argument. // indexOrSlice ??= FindIndexer(); PropertyInfo FindIndexer() { var indexers = (from p in @object.Type.GetProperties(BindingFlags.Public | BindingFlags.Instance) let i = p.GetIndexParameters() where i.Length == 1 && i[0].ParameterType == typeof(int) select p) .ToArray(); return(indexers.Length == 1 ? indexers[0] : null); } ContractUtils.RequiresNotNull(indexOrSlice, nameof(indexOrSlice)); var index = indexOrSlice as PropertyInfo ?? GetProperty(indexOrSlice as MethodInfo ?? throw Error.InvalidIndexMember(indexOrSlice)); indexOrSlice = index; // NB: Store the property rather than a method. var indexAccessor = index.GetGetMethod(nonPublic: true); // NB: System.Linq.Expressions allows non-public properties. if (indexAccessor == null) { indexAccessor = index.GetSetMethod(nonPublic: true) ?? throw LinqError.PropertyDoesNotHaveAccessor(indexOrSlice); if (indexAccessor.GetParametersCached().Length != 2) { throw LinqError.IncorrectNumberOfMethodCallArguments(indexAccessor); } } else if (indexAccessor.GetParametersCached().Length != 1) { throw LinqError.IncorrectNumberOfMethodCallArguments(indexAccessor); } if (indexAccessor.IsStatic) { throw Error.AccessorCannotBeStatic(indexAccessor); } if (!TypeUtils.IsValidInstanceType(indexAccessor, @object.Type)) { throw LinqError.PropertyNotDefinedForType(indexAccessor, @object.Type); } if (indexAccessor.GetParametersCached()[0].ParameterType != typeof(int)) { throw Error.InvalidIndexerParameterType(indexOrSlice); } ValidateMethodInfo(indexAccessor); } else { // // The type has an accessible member named Slice which has two parameters of type int. // Debug.Assert(argument.Type == typeof(Range)); indexOrSlice ??= FindSliceMethod(); MethodInfo FindSliceMethod() => @object.Type.GetMethod(@object.Type == typeof(string) ? "Substring" : "Slice", BindingFlags.Public | BindingFlags.Instance, binder: null, new[] { typeof(int), typeof(int) }, modifiers: null); ContractUtils.RequiresNotNull(indexOrSlice, nameof(indexOrSlice)); var slice = indexOrSlice as MethodInfo ?? throw Error.InvalidSliceMember(indexOrSlice); ValidateMethodInfo(slice); if (slice.IsStatic) { throw Error.SliceMethodMustNotBeStatic(slice); } ValidateCallInstanceType(@object.Type, slice); var sliceParams = slice.GetParametersCached(); if (sliceParams.Length != 2 || sliceParams[0].ParameterType != typeof(int) || sliceParams[1].ParameterType != typeof(int)) { throw Error.InvalidSliceParameters(slice); } } return(new IndexerAccessCSharpExpression(@object, argument, lengthOrCount, indexOrSlice)); }