C# 可以将 Object 附加到方法调用而不将其作为参数吗?

C# Possible to attach an Object to a method call without having it as a parameter?

我正在设计一个具有 AOP 架构 (postsharp) 的程序,它将拦截所有方法调用,但我需要一种方法来为每个调用附加一个 class。问题是我不想在每个方法调用中都显式传递 class 。那么有没有办法将 class 附加到 C# 中的方法调用?

例如,在 angular 中,我可以使用自定义拦截器将我想要的任何内容附加到 header 以用于每个拨出电话。这样可以减少重复代码。 C#中有这样的东西吗?

@Injectable()
export class CustomInterceptor implements HttpInterceptor {
  constructor() { }

  intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    request = request.clone({ withCredentials: true });        
    return next.handle(request);
  }
}

这是我在 C# 中的界面

    public class Wrapper: IMyInterface
    {       
        private IMyInterface_wrapped;

        public Wrapper(IMyInterface caller)
        {
            _wrapped = caller;
        }

        public FOO GetUserStuff(string userName)
        {
            return _wrapped.GetUserStuff(req);
        }
     }

   }

有没有办法可以像这样调用接口

          var wrapper = new Wrapper(new MyInterface());

           LoginRequest req = new LoginRequest <------ this needs to be attached to every single method call
            {
                ClientId = "ABCDEFG",
                ClientSecret = "123456"
            };

            wrapper.GetUserStuff("Username", req);   <------- My interface only takes one argument.
            wrapper.GetUserStuff("UserName").append(req) <----of course this doesn't work either

有没有一种方法可以调用接口方法并将 object 附加到它而无需在接口中实际实现它?

您可以将其设为静态 class 并在需要时调用静态方法。或者如果你想让它像 Angular 中那样,你可以将它添加到管道中(启动配置方法):

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    app.Use(async (context, next) =>
    {
        LoginRequest req = new LoginRequest
        {
            ClientId = "ABCDEFG",
            ClientSecret = "123456"
        };
        context.Response.Headers["ClientId"] = "ABCDEFG";
        await next();
    });
}

通过 DI 容器,您可以轻松地将 IOption<> 接口注入 class 构造函数:

public class Wrapper: IMyInterface
{       
    private IMyInterface_wrapped;
    private MySettings _mySettings;

    public Wrapper(IMyInterface caller, IOptions<MySettings> mySettings)
    {
        _wrapped = caller;
        _mySettings = mySettings.Value;
    }

    private LoginRequest GetLoginRequest()
    {
        return new LoginRequest
        {
            ClientId = _mySettings.ClientId,
            ClientSecret = _mySettings.ClientSecret
        };
    }

    public FOO GetUserStuff(string userName)
    {
        return _wrapped.GetUserStuff(GetLoginRequest());
    }
 }

基本上您想要的是 - 每当调用 wrapper.GetUserStuff 方法时,LoginRequest 对象可用于 Wrapper class 对象。

但是正如您在评论部分的回答,ClientIdClientSecret 的值没有改变。然后,您可以避免每次都在外部创建 LoginRequest 对象并将其作为方法参数传递到内部的整个麻烦,只需在 内部 中创建 LoginRequest 对象 [= =14=] class -

public class Wrapper : IMyInterface
{
    private IMyInterface _wrapped;
    private LoginRequest _req;

    public Wrapper(IMyInterface caller)
    {
        _wrapped = caller;
        _req = new LoginRequest { ClientId = "ABCDEFG", ClientSecret = "123456" };
    }

    public int GetUserStuff(string userName)
    {
        return _wrapped.GetUserStuff(_req);
    }
}

通常,您会将 ClientIdClientSecret 值存储在其他地方(而不是对它们进行硬编码)并相应地读取它们。

而且,如果您无法从 Wrapper class 访问 LoginRequest class(可能位于单独的 layer/project没有所需的程序集引用),那么您可以像 ClientInfo 一样声明 class 并像 -

一样使用它
public class ClientInfo
{
    public string UserName { get; set; }
    public string ClientId { get; set; }
    public string ClientSecret { get; set; }
}

public class Wrapper : IMyInterface
{
    private IMyInterface _wrapped;
    private ClientInfo _info;

    public Wrapper(IMyInterface caller)
    {
        _wrapped = caller;
        _info = new ClientInfo { ClientId = "ABCDEFG", ClientSecret = "123456" };
    }

    public int GetUserStuff(string userName)
    {
        _info.UserName = userName;
        return _wrapped.GetUserStuff(_info);
    }
}

然后 caller 可以从传递给它的 ClientInfo 创建 LoginRequest 对象。

要稍微改变@atiyar 的方法,您可以使用访问器。这是 HTTPAccessor 核心中使用的通用版本。 AsyncLocal 将为主线程设置一次,然后传播到任何衍生的线程。

public class GenericAccessor<T> where T : class
{
    private static AsyncLocal<Holder<T>> _current = new AsyncLocal<Holder<T>>();

    public T Value
    {
        get => _current.Value?.Context;
        set
        {
            var holder = _current.Value;
            if (holder != null)
            {
                // Clear current trapped in the AsyncLocals, as its done.
                holder.Context = null;
            }

            if (value != null)
            {
                // Use an object indirection to hold the in the AsyncLocal,
                // so it can be cleared in all ExecutionContexts when its cleared.
                _current.Value = new Holder<T> { Context = value };
            }
        }
    }

    private class Holder<T>
    {
        public T Context;
    }
}

随着实施

public class ClientInfo
{
    public string ClientId { get; set; }
    public string ClientSecret { get; set; }
}

public class UserInfo: ClientInfo
{
    public UserInfo(ClientInfo clientInfo)
    {
         this.ClientId = clientInfo.ClientId;
         this.ClientSecret = clientInfo.ClientSecret;
    }

    public string UserName { get; set; }
}

public interface IClientInfoAccessor
{
    ClientInfo ClientInfo { get; set; }
}

public class ClientInfoAccessor : GenericAccessor<ClientInfo>, IClientInfoAccessor
{
    public ClientInfo ClientInfo{ get => Value; set => Value = value; }
}

public class Wrapper: IMyInterface
{
    private IMyInterface _wrapped;
    private IClientInfoAccessor _accessor;

    public Wrapper(IMyInterface caller, IClientInfoAccessor accessor)
    {
        _wrapped = caller;
        _accessor = accessor;
    }

    public int GetUserStuff(string userName)
    {
        var req = new UserInfo(_accessor.ClientInfo);
        req.UserName = userName;
        return _wrapped.GetUserStuff(req);
    }
}

您需要做的就是为每个操作在中间件中设置 ClientInfo,您甚至可以在单例中的任何地方使用访问器。