| File: System\Dynamic\BindingRestrictions.cs | Web Access |
| Project: src\runtime\src\libraries\System.Linq.Expressions\src\System.Linq.Expressions.csproj (System.Linq.Expressions) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Dynamic.Utils; using System.Linq.Expressions; using System.Runtime.CompilerServices; using AstUtils = System.Linq.Expressions.Utils; namespace System.Dynamic { /// <summary> /// Represents a set of binding restrictions on the <see cref="DynamicMetaObject"/> under which the dynamic binding is valid. /// </summary> [DebuggerTypeProxy(typeof(BindingRestrictionsProxy)), DebuggerDisplay("{DebugView}")] public abstract class BindingRestrictions { /// <summary> /// Represents an empty set of binding restrictions. This field is read-only. /// </summary> public static readonly BindingRestrictions Empty = new CustomRestriction(AstUtils.Constant(true)); private const int TypeRestrictionHash = 0b_0100_1001_0010_0100_1001_0010_0100_1001; private const int InstanceRestrictionHash = unchecked((int)0b_1001_0010_0100_1001_0010_0100_1001_0010); private const int CustomRestrictionHash = 0b_0010_0100_1001_0010_0100_1001_0010_0100; private BindingRestrictions() { } // Overridden by specialized subclasses internal abstract Expression GetExpression(); /// <summary> /// Merges the set of binding restrictions with the current binding restrictions. /// </summary> /// <param name="restrictions">The set of restrictions with which to merge the current binding restrictions.</param> /// <returns>The new set of binding restrictions.</returns> public BindingRestrictions Merge(BindingRestrictions restrictions) { ArgumentNullException.ThrowIfNull(restrictions); if (this == Empty) { return restrictions; } if (restrictions == Empty) { return this; } return new MergedRestriction(this, restrictions); } /// <summary> /// Creates the binding restriction that check the expression for runtime type identity. /// </summary> /// <param name="expression">The expression to test.</param> /// <param name="type">The exact type to test.</param> /// <returns>The new binding restrictions.</returns> public static BindingRestrictions GetTypeRestriction(Expression expression, Type type) { ArgumentNullException.ThrowIfNull(expression); ArgumentNullException.ThrowIfNull(type); return new TypeRestriction(expression, type); } /// <summary> /// The method takes a DynamicMetaObject, and returns an instance restriction for testing null if the object /// holds a null value, otherwise returns a type restriction. /// </summary> internal static BindingRestrictions GetTypeRestriction(DynamicMetaObject obj) { Debug.Assert(obj != null); if (obj.Value == null && obj.HasValue) { return GetInstanceRestriction(obj.Expression, null); } else { return GetTypeRestriction(obj.Expression, obj.LimitType); } } /// <summary> /// Creates the binding restriction that checks the expression for object instance identity. /// </summary> /// <param name="expression">The expression to test.</param> /// <param name="instance">The exact object instance to test.</param> /// <returns>The new binding restrictions.</returns> public static BindingRestrictions GetInstanceRestriction(Expression expression, object? instance) { ArgumentNullException.ThrowIfNull(expression); return new InstanceRestriction(expression, instance); } /// <summary> /// Creates the binding restriction that checks the expression for arbitrary immutable properties. /// </summary> /// <param name="expression">The expression expressing the restrictions.</param> /// <returns>The new binding restrictions.</returns> /// <remarks> /// By convention, the general restrictions created by this method must only test /// immutable object properties. /// </remarks> public static BindingRestrictions GetExpressionRestriction(Expression expression) { ArgumentNullException.ThrowIfNull(expression); ContractUtils.Requires(expression.Type == typeof(bool), nameof(expression)); return new CustomRestriction(expression); } /// <summary> /// Combines binding restrictions from the list of <see cref="DynamicMetaObject"/> instances into one set of restrictions. /// </summary> /// <param name="contributingObjects">The list of <see cref="DynamicMetaObject"/> instances from which to combine restrictions.</param> /// <returns>The new set of binding restrictions.</returns> public static BindingRestrictions Combine(IList<DynamicMetaObject>? contributingObjects) { BindingRestrictions res = Empty; if (contributingObjects != null) { foreach (DynamicMetaObject mo in contributingObjects) { if (mo != null) { res = res.Merge(mo.Restrictions); } } } return res; } /// <summary> /// Builds a balanced tree of AndAlso nodes. /// We do this so the compiler won't stack overflow if we have many /// restrictions. /// </summary> private sealed class TestBuilder { private readonly HashSet<BindingRestrictions> _unique = new HashSet<BindingRestrictions>(); private readonly Stack<AndNode> _tests = new Stack<AndNode>(); private struct AndNode { internal int Depth; internal Expression Node; } internal void Append(BindingRestrictions restrictions) { if (_unique.Add(restrictions)) { Push(restrictions.GetExpression(), 0); } } internal Expression ToExpression() { Expression result = _tests.Pop().Node; while (_tests.Count > 0) { result = Expression.AndAlso(_tests.Pop().Node, result); } return result; } private void Push(Expression node, int depth) { while (_tests.Count > 0 && _tests.Peek().Depth == depth) { node = Expression.AndAlso(_tests.Pop().Node, node); depth++; } _tests.Push(new AndNode { Node = node, Depth = depth }); } } /// <summary> /// Creates the <see cref="Expression"/> representing the binding restrictions. /// </summary> /// <returns>The expression tree representing the restrictions.</returns> public Expression ToExpression() => GetExpression(); private sealed class MergedRestriction : BindingRestrictions { internal readonly BindingRestrictions Left; internal readonly BindingRestrictions Right; internal MergedRestriction(BindingRestrictions left, BindingRestrictions right) { Left = left; Right = right; } internal override Expression GetExpression() { // We could optimize this better, e.g. common subexpression elimination // But for now, it's good enough. var testBuilder = new TestBuilder(); // Visit the tree, left to right. // Use an explicit stack so we don't stack overflow. // // Left-most node is on top of the stack, so we always expand the // left most node each iteration. var stack = new Stack<BindingRestrictions>(); BindingRestrictions top = this; while (true) { if (top is MergedRestriction m) { stack.Push(m.Right); top = m.Left; } else { testBuilder.Append(top); if (stack.Count == 0) { return testBuilder.ToExpression(); } top = stack.Pop(); } } } } private sealed class CustomRestriction : BindingRestrictions { private readonly Expression _expression; internal CustomRestriction(Expression expression) { Debug.Assert(expression != null); _expression = expression; } public override bool Equals([NotNullWhen(true)] object? obj) { return obj is CustomRestriction other && other._expression == _expression; } public override int GetHashCode() => CustomRestrictionHash ^ _expression.GetHashCode(); internal override Expression GetExpression() => _expression; } private sealed class TypeRestriction : BindingRestrictions { private readonly Expression _expression; private readonly Type _type; internal TypeRestriction(Expression parameter, Type type) { Debug.Assert(parameter != null); Debug.Assert(type != null); _expression = parameter; _type = type; } public override bool Equals([NotNullWhen(true)] object? obj) { return obj is TypeRestriction other && other._expression == _expression && TypeUtils.AreEquivalent(other._type, _type); } public override int GetHashCode() => TypeRestrictionHash ^ _expression.GetHashCode() ^ _type.GetHashCode(); internal override Expression GetExpression() => Expression.TypeEqual(_expression, _type); } private sealed class InstanceRestriction : BindingRestrictions { private readonly Expression _expression; private readonly object? _instance; internal InstanceRestriction(Expression parameter, object? instance) { Debug.Assert(parameter != null); _expression = parameter; _instance = instance; } public override bool Equals([NotNullWhen(true)] object? obj) { return obj is InstanceRestriction other && other._expression == _expression && other._instance == _instance; } public override int GetHashCode() => InstanceRestrictionHash ^ RuntimeHelpers.GetHashCode(_instance!) ^ _expression.GetHashCode(); internal override Expression GetExpression() { if (_instance == null) { return Expression.Equal( Expression.Convert(_expression, typeof(object)), AstUtils.Null ); } ParameterExpression temp = Expression.Parameter(typeof(object), null); return Expression.Block( new TrueReadOnlyCollection<ParameterExpression>(temp), new TrueReadOnlyCollection<Expression>( #if ENABLEDYNAMICPROGRAMMING Expression.Assign( temp, Expression.Property( Expression.Constant(new WeakReference(_instance)), typeof(WeakReference).GetProperty("Target") ) ), #else Expression.Assign( temp, Expression.Constant(_instance, typeof(object)) ), #endif Expression.AndAlso( //check that WeakReference was not collected. Expression.NotEqual(temp, AstUtils.Null), Expression.Equal( Expression.Convert(_expression, typeof(object)), temp ) ) ) ); } } private string DebugView => ToExpression().ToString(); private sealed class BindingRestrictionsProxy { private readonly BindingRestrictions _node; public BindingRestrictionsProxy(BindingRestrictions node) { ArgumentNullException.ThrowIfNull(node); _node = node; } public bool IsEmpty => _node == Empty; public Expression Test => _node.ToExpression(); public BindingRestrictions[] Restrictions { get { var restrictions = new List<BindingRestrictions>(); // Visit the tree, left to right // // Left-most node is on top of the stack, so we always expand the // left most node each iteration. var stack = new Stack<BindingRestrictions>(); BindingRestrictions top = _node; while (true) { if (top is MergedRestriction m) { stack.Push(m.Right); top = m.Left; } else { restrictions.Add(top); if (stack.Count == 0) { return restrictions.ToArray(); } top = stack.Pop(); } } } } // To prevent fxcop warning about this field public override string ToString() => _node.DebugView; } } }