【问题标题】:Call a function implicitly after a function is called调用函数后隐式调用函数
【发布时间】:2018-10-31 07:47:02
【问题描述】:

是否可以像在单元测试中那样实现这样的行为 你可以在哪里注释

[TestInitialize]

每次在a之前执行这个函数

[TestMethod]

被执行。同样的方式

[TestCleanup]

总是在 TestMethod 之后执行。

【问题讨论】:

  • 不是开箱即用的,不是没有尝试将代码注入您的应用程序,或者没有专门对功能进行样板化
  • 话虽如此,您的问题非常广泛,除非您展示您已经尝试过的内容。您将需要一些框架来调用带有属性注释的方法。您的程序无法猜测这些属性的含义。
  • 是的.. 有可能...您需要创建自己的类,该类继承自 Attribute 类并在那里编写您的逻辑...对于您共享的示例,代码在TestMethod 属性检查是否有任何具有 TestInitialize 属性的方法并在当前方法之前执行它们,之后它检查具有 TestCleanup 属性的方法并执行它们。这需要大量使用反射。
  • 您可以查看 NUnit 源代码 github.com/nunit 以了解其工作原理。
  • 这是类似问题的链接。 Aspect Oriented Programming in .NET

标签: c# .net function call implicit


【解决方案1】:

您不能开箱即用地做到这一点。您需要编写自己的执行代码,使用反射来确定执行什么以及如何执行。下面,我将分享一个简单的例子。

首先,您需要描述方法类型的属性。

class InitMethodAttribute : Attribute
{
   public InitMethodAttribute():base()
   { }
};

class CleanupMethodAttribute : Attribute
{
   public CleanupMethodAttribute() : base()
   { }
};

class RunMethodAttribute : Attribute
{
   public RunMethodAttribute() : base()
   { }
};

我们将在示例类中使用它。请注意,此示例中的所有方法都是私有的。它们可以通过反射调用。另外,请注意,为简单起见,它们没有参数并且不返回任何内容。您可以解决此示例并将其更改为支持参数。

class Example
{
   [InitMethod]
   private void Init()
   {
      Console.WriteLine("Initializing...");
   }

   [InitMethod]
   private void InitMore()
   {
      Console.WriteLine("More initializing...");
   }

   [RunMethod]
   private void Run()
   {
      Console.WriteLine("Running...");
   }

   [CleanupMethod]
   private void Cleanup()
   {
      Console.WriteLine("Cleaning up...");
   }
}

下一个名为 executor 的类接受一个对象并查看其类型、识别方法并查看它们的属性。最终,如果在任何方法上找到RunMethod 属性,则执行该方法。在它执行之前所有的InitMethod 装饰方法和之后所有CleanupMethod 装饰方法。

   static class Executor
   {
      public static void Run(object obj)
      {
         var type = obj.GetType();
         var methods = type.GetMethods(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);

         var initMethods = new List<MethodInfo>();
         var cleanMethods = new List<MethodInfo>();
         foreach (var method in methods)
         {
            var initattrs = method.GetCustomAttributes(typeof(InitMethodAttribute));
            var cleanattrs = method.GetCustomAttributes(typeof(CleanupMethodAttribute));

            if (initattrs != null && initattrs.Count() > 0)
               initMethods.Add(method);
            else if (cleanattrs != null && cleanattrs.Count() > 0)
               cleanMethods.Add(method);
         }

         foreach (var method in methods)
         {
            var runattrs = method.GetCustomAttributes(typeof(RunMethodAttribute));
            if(runattrs != null)
            {
               var runattr = runattrs.FirstOrDefault();
               if(runattr != null)
               {
                  foreach (var m in initMethods)
                     m.Invoke(obj, null);

                  method.Invoke(obj, null);

                  foreach (var m in cleanMethods)
                     m.Invoke(obj, null);
               }
            }
         }
      }
   }

以下程序使用了所有这些:

class Program
{
   static void Main(string[] args)
   {
      var example = new Example();
      Executor.Run(example);
   }
}

输出是:

Initializing...
More initializing...
Running...
Cleaning up...

【讨论】:

  • 感谢您的精彩解释。是否有可能绕过被调用的 Executor 类?当我调用带有 Run 标记的函数时,是否可以调用 Init taggeg 函数?因此,当我放入主类 example.Run() 时,会调用 example.Init() 和 InitMore() 吗?
  • 不在这种基于反射的方法中。您可以在另一个答案中使用如下所示的模板方法模式,尽管它不像这样灵活。
  • 这就是单元测试执行器本身的工作方式。
【解决方案2】:

您正在寻找的是Aspect Oriented Programming,不幸的是(或不是,取决于您问谁)C# 没有实现任何本地机制来支持它。实现 AOP 引擎当然是可能的,但不是微不足道的,您需要使用某种动态代理(相关问题:.NET Core: attributes that execute before and after method 由 CodeConstruct 和How to make a simple dynamic proxy in C# 指出;还有here's a nice article about the subject)。

【讨论】:

  • 我不确定这是否真的是一个答案,或者只是一个广泛的评论,提供了一些指向更多信息的帖子和网站的链接。
【解决方案3】:

有几个替代解决方案。

首先,您可以使用Template Methods,其中初始化和清理在基类中定义,而您的实现类仅实现DoWorkImpl 方法。调用DoWork 将调用Initialize,然后运行在继承类中实现的DoWorkImpl 方法并以Cleanup 结束。这在(伪)代码中看起来像这样:

public abstract class BaseClass
{
    public void DoWork()
    {
        Initialize();
        DoWorkImpl();
        CleanUp();
    }

    public abstract void DoWorkImpl();

    private void Initialize()
    {
       // Initialization code here.
    }

    private void Cleanup()
    {
       // Cleanup code here.
    }
}

另一种选择是使用Actions
这在(伪)代码中看起来像这样:

public void SurroundWithInitializeAndCleanup(Action actionToWrap)
{
    Initialize();
    actionToWrap();
    Cleanup();
}

private void Initialize()
{
    // Initialization code here.
}

private void Cleanup()
{
   // Cleanup code here.
}

【讨论】:

    猜你喜欢
    • 2015-02-01
    • 2015-02-09
    • 2017-03-11
    • 1970-01-01
    • 1970-01-01
    • 2016-11-15
    • 1970-01-01
    • 2014-03-04
    • 1970-01-01
    相关资源
    最近更新 更多