Есть ли способ узнать, захвачено ли выражение ParameterExpression выражением BlockExpression или LambdaExpression - PullRequest
0 голосов
/ 25 октября 2010

Я пишу некоторый код, чтобы помочь упростить шаблоны методов C # в деревьях выражений. В случае использования блока есть три способа его использования:

using(var something=IDisposible_value) //1

using(something = IDisposible_value)   //2

using(something)                 //3

Прямо сейчас мой код выглядит так:

public static Expression GenerateUsingBoilerPlate(ParameterExpression disposible,Expression toAssign,Expression body)
{
    ArgumentValidator.AssertIsNotNull(() => disposible);
 ArgumentValidator.AssertIsNotNull(() => body);

    var toDispose = Expression.Variable(typeof(IDisposable));
    Expression retVal = Expression.TryFinally(
        body,
        Expression.Block(
            new[] { toDispose },
            Expression.Assign(
                toDispose,
                Expression.TypeAs(
                    disposible,
                    typeof(IDisposable)
                )
            ),
            Expression.IfThen(
                Expression.NotEqual(
                    toDispose,
                    Expression.Default(
                        typeof(IDisposable)
                    )
                ),
                Expression.Call(
                    toDispose, 
                    "Dispose", 
                    Type.EmptyTypes
                )
            )
        )
    );
    if (toAssign != null)
    {
        retVal = Expression.Block(
            new[] { disposible },
            Expression.Assign(
                disposible ,
                toAssign
            ),
            retVal
        );
    }
    return retVal;
}

Проблема в том, что этот код может обрабатывать только случай 1 и случай 3, потому что я не могу узнать, связана ли переменная disposible где-то еще в вашем дереве выражений. Кто-нибудь может предложить способ узнать, связан ли ParameterExpression?

Ответы [ 2 ]

0 голосов
/ 28 октября 2010

На самом деле есть 4 различных способа вызова использования. Приведенные ниже примеры и цифры примерно соответствуют вашим номерам, за исключением того, что я добавил # 4 ...

using (var a = File.CreateText(@"c:\temp\test.txt"))  //#1
{
    // a is only visible in this context
}
TextWriter w;
using(w = File.CreateText(@"c:\temp\test.txt")) //#2
{
    // w is visible outside of this context, but is only valid within the context
}
w = File.CreateText(@"c:\temp\test.txt");
using (w)                                       //#3
{
    // w is visible outside of this context, but is only valid between assignment and end of this context
}
using (File.CreateText(@"c:\temp\test.txt"))    //#4
{
    // the disposable is not visible in any context
}

Я бы порекомендовал вам взглянуть на метод Expression.CatchBlock (...) и посмотреть, как ему передаются параметры. Оператор catch аналогичен в том, что объявлена ​​локальная переменная для использования внутри инструкции. Это может означать, что ваша UsingBoilerPlate может выглядеть примерно так ...

public static Expression<Action> UsingBoilerPlate(
                 Expression disposeExpression,
                 Expression bodyExpression,
                 ParameterExpression localVariable,
                 bool unbound) { ... }
public static Expression<Action> UsingBoilerPlate(
                 Expression disposeExpression,
                 Expression bodyExpression,
                 ParameterExpression localVariable)
{  return UsingBoilerPlate(disposeExpression, bodyExpression, localVariable, true); }
public static Expression<Action> UsingBoilerPlate(
                 Expression disposeExpression,
                 Expression bodyExpression)
{  return UsingBoilerPlate(disposeExpression, bodyExpression, null); }

Чтобы справиться с каждым из 4 сценариев, вы бы назвали это так ...

var action1 =
    Expression.Lambda<Action>
    (
        Using
        (
            disposableExpression,
            bodyExpression,
            localVariable
        )
    );
action1.Compile()();

var action2 =
    Expression.Lambda<Action>
    (
        Expression.Block
        (
            new [] { localVariable },
            new Expression[] {
                Using
                (
                    disposableExpression,
                    bodyExpression,
                    localVariable,
                    false
                ),
                Expression.IfThenElse(
                    Expression.NotEqual(
                        localVariable, Expression.Constant(null)),
                    ((Expression<Action>)(() => Console.WriteLine("w is NOT null"))).Body,
                    ((Expression<Action>)(() => Console.WriteLine("w is null"))).Body
                )
            }
        )
    );
action2.Compile()();

В следующем примере используется маленькая хитрость, где using(w) эквивалентно using(w = w)

var action3 =
    Expression.Lambda<Action>
    (
        Expression.Block
        (
            new [] { localVariable },
            new Expression[] {
                Expression.Assign(localVariable, disposeExpression);
                Using
                (
                    localVariable,
                    bodyExpression,
                    localVariable,
                    false
                ),
                Expression.IfThenElse(
                    Expression.NotEqual(
                        localVariable, Expression.Constant(null)),
                    ((Expression<Action>)(() => Console.WriteLine("w is NOT null"))).Body,
                    ((Expression<Action>)(() => Console.WriteLine("w is null"))).Body
                )
            }
        )
    );
action3.Compile()();

И последнее,

var action4 =
    Expression.Lambda<Action>
    (
        Using
        (
            disposableExpression,
            bodyExpression
        )
    );
action4.Compile()();

Таким образом, вы не можете понять, связано ли выражение ParameterExpression с внешним вашим методом, но вы можете сказать своему методу, является ли он или нет.

0 голосов
/ 25 октября 2010

Вы создаете переменную toDispose (которая служит что-то в вашем на примере ) для двух из трех случаев, но на самом деле только один из ваших случаевобъявляет переменную (case # 1).В двух других случаях предполагается, что переменная уже объявлена ​​где-то еще.Итак, можете ли вы сделать toDispose параметром для вашего GenerateUsingBoilerPlate метода, а затем, если он пуст, создать toDispose ?

Это неОтветьте на свой вопрос о том, как определить, является ли переменная одноразовая уже связанной, но не можете ли вы просто предположить / потребовать ее привязки?Тогда дело № 1 и дело № 2 работают.Случай № 3 не использует одноразовый , вместо этого он использует toDispose .

EDIT
Другими словами, вам не нужночтобы узнать, связан ли одноразовый , вместо этого вам нужно, чтобы он был связан (если имеется).Если не поставляется, то требуется, чтобы toDispose было предоставлено.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...