从委托获取参数名称和值

本文关键字:参数 获取 | 更新日期: 2023-09-27 18:35:10

>我已经实现了一个通用方法,用于在我的 Web 服务方法中调用函数并捕获异常。这个想法是集中异常处理并记录异常发生的原因:

public class WebServiceHandler
{
     public T Execute<T>(Func<T> body)
            {
                //wrap everything in common try/catch
                try
                {                
                    return body();
                }
                catch (SoapException)
                {
                    //rethrow any pre-generated SOAP faults
                    throw;
                }           
                catch (Exception ex)
                {
                    Logger.AddTextToLog(Logger.LogLevel.Error, "An error occured");
                    var innerExceptionMessage = ex.InnerException != null ? ex.InnerException.Message : "";
                    throw GenerateSoapException(
                                                ex.Message,
                                                innerExceptionMessage, 
                                                SoapException.ServerFaultCode);
                }
            }
}

我在我的 Web 服务方法中使用了这样的方法:

[WebMethod]
GetXXX(string param1, string param2)
{
var request = new GetXXXRequest(param1, param2);
     return _webServiceHandler.Execute(() => _controller.GetXXX(request));
}
[WebMethod]
GetYYY(string param1)
{
var request = new GetYYYRequest(param1);
     return _webServiceHandler.Execute(() => _controller.GetYYY(request));
}

如果出现异常,我想记录用作控制器输入的参数名称和值。获取XXX(请求)和控制器。执行方法中的 GetYYY(请求) 方法。

我怎样才能做到这一点?还是有更好的方法来实现相同的目标?

从委托获取参数名称和值

我没有

尝试使用表达式,但我有一个针对代表的解决方案;

public static List<object> GetMethodParameterValues(Delegate method)
{
    var target = method.Target;
    if (target == null) return null;
    var fields = target.GetType().GetFields();
    var valueList = fields.Select(field => field.GetValue(target)).ToList();
    return valueList;
}

首先,你不能神奇地、隐式地查询运行时的信息。

确实,您可以通过实例化 StackTrace 类或调用 MethodBase.GetCurrentMethod() 方法来隐式学习当前执行方法的完整标识、其调用方、其调用方的调用方和所有堆栈跟踪(泛型方法的特定泛型参数除外)。

生成的MethodBase实例包含有关方法参数的信息,因此,您可能能够了解参数的名称,但这就是一切的结束。如果发明了一种允许您隐式探测参数或局部变量值的机制,那么一切都慢得多

你能做的,就是通过Expression<Lambda>类及其随行人员来帮助自己。这会有点慢,但您可以选择是想要可审查且易于管理的代码,还是非常难以管理和非常非常快的代码。

Expression<Lambda> LINQ

如何设法对数据库表进行全表扫描,而是了解您对查询执行的操作,并将其(在运行时)转换为 SQL 或您可能想象的任何其他语言(取决于实际的 LINQ 提供程序)。

首先,我建议将关注点分为两类:

  1. 检索名称和值(尽可能隐式)
  2. 使用名称和值(任何需要的位置)

要做到这一点,您需要考虑一个可以保存第 1 点结果的实体。在我给你的建议中,这将是一种Dictionary<string, object>但你可以做任何最适合你的事情。

我的建议可以这样使用:

public void SomeMethod(string x, int y) {
    IDictionary<string, object> paramValues = Helper.TapInto(
        () => x, 
        () => y
    );
    // paramValues["x"] an paramValues["y"] will hold the values of x and y
}

所以,进入编码位。你可以像这样编写一个Helper类:

public static class Helper {
}

在那个Helper类中,你可以发明一个静态方法(我称之为我的TapInto也许这不是它的最佳名称),它接收一个Expression<Func<object>>实例的原始数组。它使用 params 修饰符执行此操作,以便您可以轻松地将隐式 lambda 传递给它。作为返回,它为您提供了一个从stringobject的哈希表,表示"反编译"变量名称及其关联值。

就我而言,我还创建了相同方法的私有重载,它实际上是一个"扩展"方法,以使代码更清晰。

public static class Helper {
    // ... an overload of the TapInto method is about to appear right here
    public static IDictionary<string, object> TapInto(params Expression<Func<object>>[] parameterTouchers) {
        var result = new Dictionary<string, object>();
        foreach (var toucher in parameterTouchers) {
            string name;
            object value;
            toucher.TapInto(out name, out value);
            result[name] = value;
        }
        return result;
    }

因此,公共方法所做的只是遍历列表并将生成的结果累积到字典中。

接下来,让我们看看真正的魔术,它发生在toucher.TapInto(out name, out value)调用中:

public static class Helper {
    private static void TapInto(this Expression<Func<object>> @this, out string name, out object value) {
        Expression expression = @this.Body;
        if (expression is UnaryExpression)
            expression = (expression as UnaryExpression).Operand;
        name = (expression as MemberExpression).Member.Name;
        Func<object> compiledLambda = @this.Compile();
        value = compiledLambda();
    }
    // ... public helper method right here
}

我们在这里做的是用放大镜"观察"λ的内部。因为我们将使用object变量以外的东西,所以即将观察到像这样的隐式转换。

.. int someParameter ..
object obj = someParameter;

它只是隐式在实际的 C# 代码中,但实际上被编译为显式转换:

object obj = (object)someParameter;

但是您可能有一个普通的object参数,例如 object anotherParam ,在这种情况下,根本不会有转换。

这就是为什么,在观察表达式的复杂细节时,我认为我可能会找到一个转换(由UnaryExpression类表示)。

实际上这就像在说:在这种特殊情况下,我对调用代码的合同是,它可能只向我发送属于以下 2 类的内容:

  1. 即时object变量参考:() => someObjectVariable
  2. 带转换的变量引用:() => (object)x

合约还意外地指出"转换"位可以用UnaryExpression替换,例如:() => !someBool

它还指出您不能执行以下操作:

  • () => 123
  • () => a + b + c + 100
  • 或这些方向的任何其他内容

所以,总结一下:

  1. 你可以写你的好小帮手
  2. 您可以在任何想要使用它的地方使用它来生成参数名称与其值之间的映射,尽管它不是 100% 隐式的,至少如果您在没有完全重构的情况下重命名参数,它不会编译,或者如果您选择使用重构重命名参数,它将允许您重命名参数引用 (它也适用于字段、局部变量等)
  3. 代码中对它们感兴趣的部分之间传递字典,并相应地使用它们!

您可以使用 Execute 方法的另一个参数来获取函数的回调。操作或结果类型。例如,在这种情况下,字符串:

public T Execute<T>(Func<T> body, Action<string> callback)
            {
                //wrap everything in common try/catch
                try
                {  
                     //do stuff              
                    callback("results are...");
                }

您还可以使用委托:

public void CallbackDelegate( string str ); 

 public T Execute<T>(Func<T> body, CallbackDelegate callback)
            {
                //wrap everything in common try/catch
                try
                {  
                     //do stuff              
                    callback("results are...");
                }

也许是这样的:

sealed class Param
{
    public string Name
    {
        get;
        private set;
    }
    public object Value
    {
        get;
        private set;
    }
    public Param(string name, object value)
    {
        Name = name;
        Value = value;
    }
}
public T Execute<T>(Func<T> body, params Param[] parameters)
{
    //wrap everything in common try/catch
    try
    {
        return body();
    }
    catch (SoapException)
    {
        //rethrow any pre-generated SOAP faults
        throw;
    }
    catch (Exception ex)
    {
        Logger.AddTextToLog(Logger.LogLevel.Error, "An error occured");
        foreach (var parameter in parameters)
        {
            Logger.AddTextToLog(
                                Logger.LogLevel.Error,
                                string.Format(
                                              "{0} : {1}",
                                              parameter.Name,
                                              parameter.Value ?? "null"));
        }
        var innerExceptionMessage = ex.InnerException != null ? ex.InnerException.Message : "";
        throw GenerateSoapException(
                                    ex.Message,
                                    innerExceptionMessage,
                                    SoapException.ServerFaultCode);
    }
}

然后你打电话:

[WebMethod]
GetXXX(string param1, string param2)
{
    var request = new GetXXXRequest(param1, param2);
    return _webServiceHandler.Execute(() => _controller.GetXXX(request)
                                      new Parameter("param1", param1),
                                      new Parameter("param2", param2));
}

我最终所做的是将请求类作为参数添加到 Execute 方法中,如下所示:

public T Execute<T, TU>(Func<T> body, TU parameterClass) where TU : class
{
 //wrap everything in common try/catch
        try
        {                
            return body();
        }
        catch (SoapException)
        {
            //rethrow any pre-generated SOAP faults
            throw;
        }
        catch (Exception ex)
        {
            var serializedObject = ParameterUtil.GetPropertyNamesAndValues(parameterClass);                
            Logger.AddTextToLog(Logger.LogLevel.Error, string.Format("An error occured when calling braArkiv Web Services. Web service method arguments: {0}", serializedObject), ex);
            var innerExceptionMessage = ex.InnerException != null ? ex.InnerException.Message : "";
            throw GenerateSoapException(
                                        ex.Message,
                                        innerExceptionMessage,
                                        SoapException.ServerFaultCode);
        }
}

 public static class ParameterUtil
    {       
        public static string GetPropertyNamesAndValues<T>(T o) where T : class
        {
            using (var stringWriter = new StringWriter())
            {
                var xmlSerializer = new XmlSerializer(o.GetType());
                xmlSerializer.Serialize(stringWriter, o);
                stringWriter.Close();
                return stringWriter.ToString();
            }            
        }        
    }

用法:

[WebMethod]
GetXXX(string param1, string param2)
{
var request = new GetXXXRequest(param1, param2);
     return _webServiceHandler.Execute(() => _controller.GetXXX(request), request);
}

当发生异常时,我只需序列化包含 Web 服务方法参数的 parameterClass 参数并将字符串添加到我的日志中。

谢谢大家对我的问题的建设性意见!