如何创建一个可以检查方法参数和返回值的属性?

时间:2017-07-04 18:30:19

标签: c# .net attributes

我希望能够在方法上放置一个属性,并让该方法检查方法参数和返回值。伪代码。

[AttributeUsage(AttributeTargets.Method)]
class MyAttribute: Attribute {
   public void MethodEnter(MethodInfo info) {
      foreach (var param in info.MethodParameters {
        Console.WriteLine(param.ToString());   
      }
   }

   public void MethodLeave(MethodInfo info) {
      Console.WriteLine(info.ReturnValue);   
   }
}

public class MyClass {
  [MyAttribute]
  public SomeType foo(Order order, List<OrderLine> orderLines) {
     ...
  }
}

这可能吗?

基本上,我希望能够记录所有内容并离开该功能。我之前使用PostSharp完成了这项工作,但使用它进行日志记录似乎有些过分。

2 个答案:

答案 0 :(得分:1)

您基本上寻找AOP(面向方面​​编程)解决方案。 PostSharp在编译后在二进制文件中使用IL注入,在我看来,这是我使用过的最糟糕的框架。

我建议你装饰你的课程,而不是寻找一些非常复杂的解决方案。例如:

public MyClass : IMyClass
{
    public object[] MyMethod(object[] args){...}
}

public LoggerDecoratedMyClass : IMyClass
{
    private readonly IMyClass _inner;
    private readonly ILogger _logger;
    public LoggerDecoratedMyClass(IMyClass inner, ILogger logger)
    {
        _inner = inner;
        _logger = logger;
    } 

    public object[] MyMethod(object[] args)
    {
        try
        {
            var result = _inner.MyMethod(args);
            _logger.LogSuccess(...);
            return result;
        }
        catch (Exception ex)
        {
            _logger.LogError(..., ex);
            throw;
        }
    }
}

这看起来比属性绑定好很多,并且为您提供了控制 Aspect dependecies 的能力。此外,它强制您编写面向接口的代码。

<强>更新

另外,我经常使用范围记录:

internal struct ScopeLogger : IDisposable
{
    private readonly string _name;
    public ScopeLogger(ILogger logger, string scopeName, object[] args)
    {
        _name = scopeName;
        _logger = logger;
        _logger.LogInfo("Begin {name}: {args}", _name, args);
    }
    public void Dispose()
    {
        _logger.LogInfo("End {name}",_name);
    }
}

public static IDisposable LogScope(this ILogger logger, string name, params object[] args)
{
    return new ScopeLogger(logger, name, args);
}

只需像这样使用它:

public LoggerDecoratedMyClass : IMyClass
{
    private readonly IMyClass _inner;
    private readonly ILogger _logger;
    public LoggerDecoratedMyClass(IMyClass inner, ILogger logger)
    {
        _inner = inner;
        _logger = logger;
    } 

    public object[] MyMethod(object[] args)
    {
        using(_logger.LogScope(nameof(MyMethod), args))
        {
            return _inner.MyMethod(args);
        }
    }
}

这种装饰器看起来好多又短。

答案 1 :(得分:1)

您可以通过使您的类派生自 ContextBoundObject 以及您的属性来自 ContextAttribute 来创建方面。通过阅读this文章,我制作了以下满足您要求的样本:

首先,我们的Foo类派生自ContextBoundObject,使用我们的自定义类属性

进行修饰
 [Inpectable]
    internal class Foo : ContextBoundObject
    {
        [InspectableProperty]
        public int DoSomething(int a)
        {
            Console.WriteLine("I am doing something");
            a += 1;
            return a;
        }

        public void IamNotLogged()
        {
            Console.WriteLine("Lol");
        }

        [InspectableProperty]
        public string IamLoggedToo()
        {
            var msg = "Lol too";
            Console.WriteLine(msg);
            return msg;
        }
    }

现在是Inspectable属性

[AttributeUsage(AttributeTargets.Class)]
    public class Inpectable : ContextAttribute
    {
        public Inpectable() : base("Inspectable")
        {
        }

        public override void GetPropertiesForNewContext(IConstructionCallMessage ccm)
        {
            ccm.ContextProperties.Add(new InspectorProperty());
        }
    }

InspectablePropertyAttribute仅用于识别应记录的那些方法:

[AttributeUsage(AttributeTargets.Method)]
    public class InspectableProperty : Attribute
    {
    }

InspectorProperty,它将捕获实例的上下文并拦截将它们传递给我们方面的消息

public class InspectorProperty : IContextProperty,
    IContributeObjectSink
{
    public bool IsNewContextOK(Context newCtx) => true;

    public void Freeze(Context newContext)
    {
    }

    public string Name { get; } = "LOL";

    public IMessageSink GetObjectSink(MarshalByRefObject o,IMessageSink next) => new InspectorAspect(next);
}

魔法的作用,我们的InspectorAspect的实现:

internal class InspectorAspect : IMessageSink
    {
        internal InspectorAspect(IMessageSink next)
        {
            NextSink = next;
        }

        public IMessageSink NextSink { get; }

        public IMessage SyncProcessMessage(IMessage msg)
        {
            if (!(msg is IMethodMessage)) return NextSink.SyncProcessMessage(msg);

            var call = (IMethodMessage) msg;
            var type = Type.GetType(call.TypeName);
            if (type == null) return NextSink.SyncProcessMessage(msg);

            var methodInfo = type.GetMethod(call.MethodName);
            if (!Attribute.IsDefined(methodInfo, typeof (InspectableProperty)))
                return NextSink.SyncProcessMessage(msg);
            Console.WriteLine($"Entering method: {call.MethodName}. Args being:");
            foreach (var arg in call.Args)
                Console.WriteLine(arg);
            var returnMethod = NextSink.SyncProcessMessage(msg) as IMethodReturnMessage;

            Console.WriteLine($"Method {call.MethodName} returned: {returnMethod?.ReturnValue}");
            Console.WriteLine();
            return returnMethod;
        }

        public IMessageCtrl AsyncProcessMessage(IMessage msg, IMessageSink replySink)
        {
            throw new InvalidOperationException();
        }
    }

SyncProcessMessage 中,theInspectorAspect接收有关上下文(Foo)的所有消息,因此我们只过滤那些属于 IMethodMessage 的实例,然后只过滤属于装饰方法的那些使用 InspectableProperty 属性。

这可能不是一个随时可用的解决方案,但我认为这会让您走上正确的研究途径,获取更多信息。

最后测试:

 private static void Main(string[] args)
        {
            var foo = new Foo();
            foo.DoSomething(1);
            foo.IamNotLogged();
            foo.IamLoggedToo();
            Console.ReadLine();
        }

输出:

enter image description here

编辑&gt;&GT;&GT;

这些是所需的命名空间:

  • 使用System.Runtime.Remoting.Activation;
  • 使用System.Runtime.Remoting.Contexts;
  • 使用System.Runtime.Remoting.Messaging;