从委托获取参数名称和值

时间:2013-10-14 09:59:40

标签: c# generics delegates func

我已经实现了一种泛型方法,用于在我的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);
                }
            }
}

我在我的网络服务方法中使用这样的方法:

[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));
}

如果出现异常,我想在Execute方法中记录用作controller.GetXXX(request)和controller.GetYYY(request)方法的输入的参数名称和值。

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

5 个答案:

答案 0 :(得分:1)

首先,你无法神奇地,隐式地在运行时查询该信息。

通过实例化{{1>,您可以隐式学习当前正在执行的方法,调用者,调用者的调用者和所有堆栈跟踪的完整标识(通用方法除特殊通用参数外) }}或通过调用StackTrace方法。

生成的MethodBase.GetCurrentMethod()实例也保存有关方法参数的信息,因此,您可能能够学习参数的名称,但这就是它的全部结束。如果一个允许你隐式探测参数或局部变量值的机制就会被发明,那么一切慢很多

可以做什么,通过MethodBase班级及其随行人员来帮助自己。它会有点慢,但你可以选择是否需要可审查和易于管理的代码,或者是难以管理和非常快速的代码。

Expression<Lambda>是LINQ如何管理对数据库表进行全表扫描,而是理解您对查询所执行的操作并将其翻译(在运行时)到SQL或您可能想到的任何其他语言(取决于实际的LINQ提供程序)。

首先,我建议将问题分为两类:

  1. 检索名称和值(尽可能隐式)
  2. 使用名称和值(您想要的任何地方)
  3. 为了实现这一点,你需要考虑一个可以保持第1点结果的实体。在我给你的建议中,这将是Expression<Lambda>的一种,但你可以做任何最适合你的事。

    我的建议可以这样使用:

    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>>修饰符执行此操作,以便您可以轻松地将隐式lambda传递给它。作为返回,它为您提供从paramsstring的哈希表,表示“反编译”变量名称及其关联值。

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

    object

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

    接下来让我们看看真正的魔术,它发生在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)

    我们在这里做的是“我们正在用放大镜查看lambda”。 因为我们要使用除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

    仅隐含在实际的C#代码中,但实际上是作为显式转换编译的:

    .. int someParameter ..
    object obj = someParameter;
    

    但您可能有一个简单的object obj = (object)someParameter; 参数,例如object,在这种情况下根本就没有转化。

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

    实际上它就像是说:在这种特殊情况下,我与调用代码的契约是可能只向我发送属于这两类的东西:

    1. 立即UnaryExpression变量引用:object
    2. 包含转化的变量引用:() => someObjectVariable
    3. 合同还偶然声明“转化”位可以替换为() => (object)x,例如:UnaryExpression

      它还声明你不能做类似的事情:

      • () => !someBool
      • () => 123
      • 或其他任何指示

      所以,把它包起来:

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

答案 1 :(得分:1)

我没有尝试过表达式,但我有代表的解决方案;

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;
}

答案 2 :(得分:0)

您可以使用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...");
                }

答案 3 :(得分:0)

也许是这样的:

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));
}

答案 4 :(得分:0)

我最终做的是将请求类作为参数添加到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参数,并将该字符串添加到我的日志中。

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