Заставляет компилятор жаловаться, если статический порядок вызова пользовательских функций отменяет некоторые правила

#c# #.net #compiler-construction

#c# #.net #компилятор-построение

Вопрос:

Рассмотрим этот код.

 public class Class1
{
    public void ThisShouldNotCompileBecauseOrderWasVoilated()
    {
        Call2();
        Call1();
    }

    public void ThisShouldCompileBecauseProperOrderIsPresent()
    {
        Call1();
        Call2();
    }

    private void Call1()
    {
        // some code
    }

    private void Call2()
    {
        // some more code
    }
}
  

Какой код (или атрибут) я должен добавить в Call1() / Call2(), который гарантирует, что компилятор подаст жалобу на 1-й метод и пропустит 2-й метод. Будет некоторый список правил, к которым компилятору придется обратиться, если порядок неправильный. В этом примере в списке правил может быть указано «Call1 Call2», что означает вызов Call1() перед Call2()

Это для языка C # для .NET 4.0

Спасибо!

Комментарии:

1. Просто любопытно, почему вы хотите сделать это во время компиляции? Легко построить сценарии, включающие условную логику, где последовательность вызовов была бы в порядке во время выполнения, но не было бы способа узнать во время компиляции.

2. Я не думаю, что для этого существует какая-либо проверка во время компиляции. Однако проверки во время выполнения должны быть простыми.

3. Если вы пытаетесь защитить себя от внешних вызывающих устройств, а Call1 / Call2 всегда вызываются вместе, то вы могли бы предоставить единственную общедоступную функцию (которая принимает параметры для вызовов 1 и 2 и делегат действия), которая вызывает Call1, вызывает делегат (чтобы вызывающий мог запускать код между ними), а затем вызывает Call2 . Не могу придумать, что вы можете сделать в вашем примере, где вы имеете дело с вызывающими в пределах одного класса.

4. Это не так просто. У меня устаревшая система, где Call1, Call2 уже закодированы и распространены повсюду. Теперь известно, что если Call2 когда-либо вызывается, Call1 никогда не следует вызывать после этого. Итак, я подумал, могу ли я использовать компилятор, чтобы разобраться в этом.

Ответ №1:

В обычном C # нет ничего, что вы могли бы указать для этого.

Вы можете использовать что-то вроде NDepend для обнаружения этого, но я не уверен.

Комментарии:

1. Да, похоже на то. Оценит другие параметры. Спасибо.

Ответ №2:

Вы можете создать свой собственный атрибут и пометить свои методы с его помощью. Затем создайте правило FxCop. FxCop полностью интегрируется с вашим процессом сборки, и пока оба вызова выполняются в рамках одного метода, правило должно быть довольно простым для конкретизации.

Комментарии:

1. Спасибо, разберусь с этим.

Ответ №3:

компилятор не может принудительно выполнять порядок вызовов методов, поскольку во многих случаях он не может статически определить порядок вызовов. Например:

 public void whichOrder(boolean b)
{
   if (b) call1();
   if (!b) call2();
   if (b) call2();
   if (!b) call1();
}
  

Если необходимо, чтобы методы вызывались в правильном порядке, у вас есть несколько вариантов:

  • документируйте порядок вызовов, чтобы вызывающие пользователи знали, что делать. Это не обеспечивает соблюдения порядка, но, по крайней мере, информирует об этом программистов.
  • добавьте состояние к вашему объекту, чтобы запомнить, какой метод был вызван последним, и подтвердите, что текущий вызванный метод разрешен следующим. Это приводит к принудительной проверке метода во время выполнения.
  • Используйте макет фреймворка (например, Moq) для модульного тестирования ваших клиентов. Во время сборки проверяется правильность порядка.

Выбранный вами подход зависит от того, насколько важен правильный порядок, и от последствий вызова методов в неправильном порядке.

Альтернативой является переработка вашего дизайна, чтобы порядок методов не стал проблемой. Например, оберните оба метода третьим, call3(), который вызывает call1() и call2() в правильном порядке. Или, возможно, попросите call2() вызвать call1(), если он еще не был выполнен, и попросите call1() проверить, запущен ли он уже, и автоматически вернуть, если его не нужно запускать. Если клиенты вызывают call2(), а затем call1(), вы все равно сначала получаете эффект call1() от внутреннего вызова call2() к call1()), а вызов клиентом call1() не приводит к операции.

Например.

 public void call3()
{
    call1();
    call2();
}
  

или

 public void call2()
{
   call1();
   // rest of call2's logic
}

private boolean call1Called = false;
pubic void call1()
{
   if (!call1Called)
   {
      call1Called=true;
      call1Impl();
   }

}
  

Ответ №4:

Это не совсем то, о чем вы просите… но вы могли бы ввести другой класс:

 public class Another1
{
    public Another2 Call1()
    {
        // some code
        return new Another2();  
        // could pass 'this' to Another2 constructor so it has all state
    }
}

public class Another2
{
    public void Call2()
    {
        // some more code
    }
}
  

Теперь, начиная с экземпляра Another1, вы можете делать только obj.Call1().Call2() и никогда obj.Call2().Call1() . Что еще лучше, это принудительное выполнение находится в IDE по мере ввода. Взгляните также на шаблоны ‘fluent’.

Комментарии:

1. В моем случае это будет невозможно. Спасибо.