Пример #1
0
        public static string GetExpressionText(LambdaExpression expression, ExpressionTextCache expressionTextCache)
        {
            if (expression == null)
            {
                throw new ArgumentNullException(nameof(expression));
            }

            if (expressionTextCache != null &&
                expressionTextCache.Entries.TryGetValue(expression, out var expressionText))
            {
                return(expressionText);
            }

            // Determine size of string needed (length) and number of segments it contains (segmentCount). Put another
            // way, segmentCount tracks the number of times the loop below should iterate. This avoids adding ".model"
            // and / or an extra leading "." and then removing them after the loop. Other information collected in this
            // first loop helps with length and segmentCount adjustments. doNotCache is somewhat separate: If
            // true, expression strings are not cached for the expression.
            //
            // After the corrections below the first loop, length is usually exactly the size of the returned string.
            // However when containsIndexers is true, the calculation is approximate because either evaluating indexer
            // expressions multiple times or saving indexer strings can get expensive. Optimizing for the common case
            // of a collection (not a dictionary) with less than 100 elements. If that assumption proves to be
            // incorrect, the StringBuilder will be enlarged but hopefully just once.
            var doNotCache   = false;
            var lastIsModel  = false;
            var length       = 0;
            var segmentCount = 0;
            var trailingMemberExpressions = 0;

            var part = expression.Body;

            while (part != null)
            {
                switch (part.NodeType)
                {
                case ExpressionType.Call:
                    // Will exit loop if at Method().Property or [i,j].Property. In that case (like [i].Property),
                    // don't cache and don't remove ".Model" (if that's .Property).
                    doNotCache  = true;
                    lastIsModel = false;

                    var methodExpression = (MethodCallExpression)part;
                    if (IsSingleArgumentIndexer(methodExpression))
                    {
                        length += "[99]".Length;
                        part    = methodExpression.Object;
                        segmentCount++;
                        trailingMemberExpressions = 0;
                    }
                    else
                    {
                        // Unsupported.
                        part = null;
                    }
                    break;

                case ExpressionType.ArrayIndex:
                    var binaryExpression = (BinaryExpression)part;

                    doNotCache  = true;
                    lastIsModel = false;
                    length     += "[99]".Length;
                    part        = binaryExpression.Left;
                    segmentCount++;
                    trailingMemberExpressions = 0;
                    break;

                case ExpressionType.MemberAccess:
                    var memberExpressionPart = (MemberExpression)part;
                    var name = memberExpressionPart.Member.Name;

                    // If identifier contains "__", it is "reserved for use by the implementation" and likely
                    // compiler- or Razor-generated e.g. the name of a field in a delegate's generated class.
                    if (name.Contains("__"))
                    {
                        // Exit loop.
                        part = null;
                    }
                    else
                    {
                        lastIsModel = string.Equals("model", name, StringComparison.OrdinalIgnoreCase);
                        length     += name.Length + 1;
                        part        = memberExpressionPart.Expression;
                        segmentCount++;
                        trailingMemberExpressions++;
                    }
                    break;

                case ExpressionType.Parameter:
                    // Unsupported but indicates previous member access was not the view's Model.
                    lastIsModel = false;
                    part        = null;
                    break;

                default:
                    // Unsupported.
                    part = null;
                    break;
                }
            }

            // If name would start with ".model", then strip that part away.
            if (lastIsModel)
            {
                length -= ".model".Length;
                segmentCount--;
                trailingMemberExpressions--;
            }

            // Trim the leading "." if present. The loop below special-cases the last property to avoid this addition.
            if (trailingMemberExpressions > 0)
            {
                length--;
            }

            Debug.Assert(segmentCount >= 0);
            if (segmentCount == 0)
            {
                Debug.Assert(!doNotCache);
                if (expressionTextCache != null)
                {
                    expressionTextCache.Entries.TryAdd(expression, string.Empty);
                }

                return(string.Empty);
            }

            var builder = new StringBuilder(length);

            part = expression.Body;
            while (part != null && segmentCount > 0)
            {
                segmentCount--;
                switch (part.NodeType)
                {
                case ExpressionType.Call:
                    Debug.Assert(doNotCache);
                    var methodExpression = (MethodCallExpression)part;

                    InsertIndexerInvocationText(builder, methodExpression.Arguments.Single(), expression);

                    part = methodExpression.Object;
                    break;

                case ExpressionType.ArrayIndex:
                    Debug.Assert(doNotCache);
                    var binaryExpression = (BinaryExpression)part;

                    InsertIndexerInvocationText(builder, binaryExpression.Right, expression);

                    part = binaryExpression.Left;
                    break;

                case ExpressionType.MemberAccess:
                    var memberExpression = (MemberExpression)part;
                    var name             = memberExpression.Member.Name;
                    Debug.Assert(!name.Contains("__"));

                    builder.Insert(0, name);
                    if (segmentCount > 0)
                    {
                        // One or more parts to the left of this part are coming.
                        builder.Insert(0, '.');
                    }

                    part = memberExpression.Expression;
                    break;

                default:
                    // Should be unreachable due to handling in above loop.
                    Debug.Assert(false);
                    break;
                }
            }

            Debug.Assert(segmentCount == 0);
            expressionText = builder.ToString();
            if (expressionTextCache != null && !doNotCache)
            {
                expressionTextCache.Entries.TryAdd(expression, expressionText);
            }

            return(expressionText);
        }
Пример #2
0
        public static string GetExpressionText(LambdaExpression expression, ExpressionTextCache expressionTextCache)
        {
            if (expression == null)
            {
                throw new ArgumentNullException(nameof(expression));
            }

            string expressionText;

            if (expressionTextCache != null &&
                expressionTextCache.Entries.TryGetValue(expression, out expressionText))
            {
                return(expressionText);
            }

            var containsIndexers = false;
            var part             = expression.Body;

            // Builder to concatenate the names for property/field accessors within an expression to create a string.
            var builder = new StringBuilder();

            while (part != null)
            {
                if (part.NodeType == ExpressionType.Call)
                {
                    containsIndexers = true;
                    var methodExpression = (MethodCallExpression)part;
                    if (!IsSingleArgumentIndexer(methodExpression))
                    {
                        // Unsupported.
                        break;
                    }

                    InsertIndexerInvocationText(
                        builder,
                        methodExpression.Arguments.Single(),
                        expression);

                    part = methodExpression.Object;
                }
                else if (part.NodeType == ExpressionType.ArrayIndex)
                {
                    containsIndexers = true;
                    var binaryExpression = (BinaryExpression)part;

                    InsertIndexerInvocationText(
                        builder,
                        binaryExpression.Right,
                        expression);

                    part = binaryExpression.Left;
                }
                else if (part.NodeType == ExpressionType.MemberAccess)
                {
                    var memberExpressionPart = (MemberExpression)part;
                    var name = memberExpressionPart.Member.Name;

                    // If identifier contains "__", it is "reserved for use by the implementation" and likely compiler-
                    // or Razor-generated e.g. the name of a field in a delegate's generated class.
                    if (name.Contains("__"))
                    {
                        // Exit loop. Should have the entire name because previous MemberAccess has same name as the
                        // leftmost expression node (a variable).
                        break;
                    }

                    builder.Insert(0, name);
                    builder.Insert(0, '.');
                    part = memberExpressionPart.Expression;
                }
                else
                {
                    break;
                }
            }

            // If parts start with "model", then strip that part away.
            if (part == null || part.NodeType != ExpressionType.Parameter)
            {
                var text = builder.ToString();
                if (text.StartsWith(".model", StringComparison.OrdinalIgnoreCase))
                {
                    // 6 is the length of the string ".model".
                    builder.Remove(0, 6);
                }
            }

            if (builder.Length > 0)
            {
                // Trim the leading "." if present.
                builder.Replace(".", string.Empty, 0, 1);
            }

            expressionText = builder.ToString();

            if (expressionTextCache != null && !containsIndexers)
            {
                expressionTextCache.Entries.TryAdd(expression, expressionText);
            }

            return(expressionText);
        }