/// <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));
        }