|
// 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.Collections.ObjectModel;
using System.Diagnostics;
using System.Dynamic.Utils;
namespace System.Linq.Expressions
{
/// <summary>
/// Represents a try/catch/finally/fault block.
///
/// The body is protected by the try block.
/// The handlers consist of a set of <see cref="CatchBlock"/>s that can either be catch or filters.
/// The fault runs if an exception is thrown.
/// The finally runs regardless of how control exits the body.
/// Only one of fault or finally can be supplied.
/// The return type of the try block must match the return type of any associated catch statements.
/// </summary>
[DebuggerTypeProxy(typeof(TryExpressionProxy))]
public sealed class TryExpression : Expression
{
internal TryExpression(Type type, Expression body, Expression? @finally, Expression? fault, ReadOnlyCollection<CatchBlock> handlers)
{
Type = type;
Body = body;
Handlers = handlers;
Finally = @finally;
Fault = fault;
}
/// <summary>
/// Gets the static type of the expression that this <see cref="Expression"/> represents. (Inherited from <see cref="Expression"/>.)
/// </summary>
/// <returns>The <see cref="System.Type"/> that represents the static type of the expression.</returns>
public sealed override Type Type { get; }
/// <summary>
/// Returns the node type of this <see cref="Expression"/>. (Inherited from <see cref="Expression"/>.)
/// </summary>
/// <returns>The <see cref="ExpressionType"/> that represents this expression.</returns>
public sealed override ExpressionType NodeType => ExpressionType.Try;
/// <summary>
/// Gets the <see cref="Expression"/> representing the body of the try block.
/// </summary>
public Expression Body { get; }
/// <summary>
/// Gets the collection of <see cref="CatchBlock"/>s associated with the try block.
/// </summary>
public ReadOnlyCollection<CatchBlock> Handlers { get; }
/// <summary>
/// Gets the <see cref="Expression"/> representing the finally block.
/// </summary>
public Expression? Finally { get; }
/// <summary>
/// Gets the <see cref="Expression"/> representing the fault block.
/// </summary>
public Expression? Fault { get; }
/// <summary>
/// Dispatches to the specific visit method for this node type.
/// </summary>
protected internal override Expression Accept(ExpressionVisitor visitor)
{
return visitor.VisitTry(this);
}
/// <summary>
/// Creates a new expression that is like this one, but using the
/// supplied children. If all of the children are the same, it will
/// return this expression.
/// </summary>
/// <param name="body">The <see cref="Body"/> property of the result.</param>
/// <param name="handlers">The <see cref="Handlers"/> property of the result.</param>
/// <param name="finally">The <see cref="Finally"/> property of the result.</param>
/// <param name="fault">The <see cref="Fault"/> property of the result.</param>
/// <returns>This expression if no children changed, or an expression with the updated children.</returns>
public TryExpression Update(Expression body, IEnumerable<CatchBlock>? handlers, Expression? @finally, Expression? fault)
{
if (body == Body & @finally == Finally & fault == Fault)
{
if (ExpressionUtils.SameElements(ref handlers!, Handlers))
{
return this;
}
}
return MakeTry(Type, body, @finally, fault, handlers);
}
}
public partial class Expression
{
/// <summary>
/// Creates a <see cref="TryExpression"/> representing a try block with a fault block and no catch statements.
/// </summary>
/// <param name="body">The body of the try block.</param>
/// <param name="fault">The body of the fault block.</param>
/// <returns>The created <see cref="TryExpression"/>.</returns>
public static TryExpression TryFault(Expression body, Expression? fault)
{
return MakeTry(null, body, null, fault, handlers: null);
}
/// <summary>
/// Creates a <see cref="TryExpression"/> representing a try block with a finally block and no catch statements.
/// </summary>
/// <param name="body">The body of the try block.</param>
/// <param name="finally">The body of the finally block.</param>
/// <returns>The created <see cref="TryExpression"/>.</returns>
public static TryExpression TryFinally(Expression body, Expression? @finally)
{
return MakeTry(null, body, @finally, fault: null, handlers: null);
}
/// <summary>
/// Creates a <see cref="TryExpression"/> representing a try block with any number of catch statements and neither a fault nor finally block.
/// </summary>
/// <param name="body">The body of the try block.</param>
/// <param name="handlers">The array of zero or more <see cref="CatchBlock"/>s representing the catch statements to be associated with the try block.</param>
/// <returns>The created <see cref="TryExpression"/>.</returns>
public static TryExpression TryCatch(Expression body, params CatchBlock[]? handlers)
{
return MakeTry(null, body, null, null, handlers);
}
/// <summary>
/// Creates a <see cref="TryExpression"/> representing a try block with any number of catch statements and a finally block.
/// </summary>
/// <param name="body">The body of the try block.</param>
/// <param name="finally">The body of the finally block.</param>
/// <param name="handlers">The array of zero or more <see cref="CatchBlock"/>s representing the catch statements to be associated with the try block.</param>
/// <returns>The created <see cref="TryExpression"/>.</returns>
public static TryExpression TryCatchFinally(Expression body, Expression? @finally, params CatchBlock[]? handlers)
{
return MakeTry(null, body, @finally, null, handlers);
}
/// <summary>
/// Creates a <see cref="TryExpression"/> representing a try block with the specified elements.
/// </summary>
/// <param name="type">The result type of the try expression. If null, body and all handlers must have identical type.</param>
/// <param name="body">The body of the try block.</param>
/// <param name="finally">The body of the finally block. Pass null if the try block has no finally block associated with it.</param>
/// <param name="fault">The body of the t block. Pass null if the try block has no fault block associated with it.</param>
/// <param name="handlers">A collection of <see cref="CatchBlock"/>s representing the catch statements to be associated with the try block.</param>
/// <returns>The created <see cref="TryExpression"/>.</returns>
public static TryExpression MakeTry(Type? type, Expression body, Expression? @finally, Expression? fault, IEnumerable<CatchBlock>? handlers)
{
ExpressionUtils.RequiresCanRead(body, nameof(body));
ReadOnlyCollection<CatchBlock> @catch = handlers.ToReadOnly();
ContractUtils.RequiresNotNullItems(@catch, nameof(handlers));
ValidateTryAndCatchHaveSameType(type, body, @catch);
if (fault != null)
{
if (@finally != null || @catch.Count > 0)
{
throw Error.FaultCannotHaveCatchOrFinally(nameof(fault));
}
ExpressionUtils.RequiresCanRead(fault, nameof(fault));
}
else if (@finally != null)
{
ExpressionUtils.RequiresCanRead(@finally, nameof(@finally));
}
else if (@catch.Count == 0)
{
throw Error.TryMustHaveCatchFinallyOrFault();
}
return new TryExpression(type ?? body.Type, body, @finally, fault, @catch);
}
//Validate that the body of the try expression must have the same type as the body of every try block.
private static void ValidateTryAndCatchHaveSameType(Type? type, Expression tryBody, ReadOnlyCollection<CatchBlock> handlers)
{
Debug.Assert(tryBody != null);
// Type unification ... all parts must be reference assignable to "type"
if (type != null)
{
if (type != typeof(void))
{
if (!TypeUtils.AreReferenceAssignable(type, tryBody.Type))
{
throw Error.ArgumentTypesMustMatch();
}
foreach (CatchBlock cb in handlers)
{
if (!TypeUtils.AreReferenceAssignable(type, cb.Body.Type))
{
throw Error.ArgumentTypesMustMatch();
}
}
}
}
else if (tryBody.Type == typeof(void))
{
//The body of every try block must be null or have void type.
foreach (CatchBlock cb in handlers)
{
Debug.Assert(cb.Body != null);
if (cb.Body.Type != typeof(void))
{
throw Error.BodyOfCatchMustHaveSameTypeAsBodyOfTry();
}
}
}
else
{
//Body of every catch must have the same type of body of try.
type = tryBody.Type;
foreach (CatchBlock cb in handlers)
{
Debug.Assert(cb.Body != null);
if (!TypeUtils.AreEquivalent(cb.Body.Type, type))
{
throw Error.BodyOfCatchMustHaveSameTypeAsBodyOfTry();
}
}
}
}
}
}
|