【问题标题】:How can I refactor our the type parameter from this code?如何从此代码重构我们的类型参数?
【发布时间】:2010-06-22 20:17:28
【问题描述】:

我想编写一个扩展方法来测试某个属性是否应用于方法调用,并且我想将该方法指定为 lambda 表达式。目前,我有以下(工作)方法,但我真的不喜欢这段代码的样子:

// Signature of my extension method:
public static bool HasAttribute<TAttribute, TDelegate>(this Expression<TDelegate> method)

// Usage (current code)
Expression<Func<AccountController, LogInModel, string, ActionResult>> mut = (c, m, s) => c.LogIn(m, s);
mut.HasAttribute<ExportModelStateAttribute, Func<AccountController, LogInModel, string, ActionResult>>().ShouldBeTrue();

如你所见,我必须指定委托类型两次,而且两次都不好看...我想要更像的东西

// Usage (if I had my way...)
var mut = (c, m, s) => c.LogIn(m, s);
mut.HasAttribute<ExportModelStateAttribute>().ShouldBeTrue();

但我意识到这可能有点过分了。

有什么方法可以重构掉当前代码中的类型参数?

我首先在那里有TDelegate 类型参数的原因是,我想使用它而不考虑方法签名和返回类型,并且取决于输入参数的数量以及所讨论的方法是否是void 或函数,TDelegate 需要变化。我不想对我测试的方法的每个输入参数数量都有一个不同的实现......

更新:
正如 Jay 在评论中指出的那样,我显然不需要在对 HasAttribute&lt;&gt; 的调用中指定 TDelegate 类型参数。现在的代码如下所示:

Expression<Func<AccountController, LogInModel, string, ActionResult>> mut = (c, m, s) => c.LogIn(m, s);
mut.HasAttribute<ExportModelStateAttribute>().ShouldBeTrue();

它好多了,但我仍然认为第一行相当混乱。会不会更好?

【问题讨论】:

  • 为什么需要在HasAttribute扩展方法中提供第二个类型参数?
  • @Jay,你是对的——看来我没有。但是,我仍然觉得代码很丑陋。会不会更好?
  • @Jay,已经收集了新的事实:如果我确实提供了第二种类型的参数,那么一切正常。如果我不这样做,我的测试将失败(因为隐式提供的TDelegate 或Body 属性都不是MethodCallExpression...)

标签: c# generics refactoring delegates inferred-type


【解决方案1】:

您可以做的一件事是将长而丑陋的Func&lt;AccountController, LogInModel, string, ActionResult&gt; 替换为

public delegate ActionResult myDelegate(AccountController accountController, LogInModel logInModel, string varString);

Expression<myDelegate> mut = (c, m, s) => c.LogIn(m, s);
mut.HasAttribute<ExportModelStateAttribute>().ShouldBeTrue();

更新: 显示更新问题的示例。我不认为它可以变得更简单。

【讨论】:

  • 这确实有助于整理一些东西,但我仍然必须指定委托的类型,即使你已经将我必须这样做的次数减半。我不明白为什么 C# 不能从 lambda 中看到它是什么类型?毕竟,=&gt; 之后的部分真的只能评估为其中之一......
  • @Tomas Lambda 表达式用于许多事情——Expression 只是其中之一。例如,编译器无法知道您需要 Expression&lt;Func&lt;T1,T2,TResult&gt;&gt; 而不是 Func&lt;T1,T2,TResult&gt;。
【解决方案2】:
  1. 你能用LambdaExpression代替通用形式Expression&lt;&gt;吗? (如果是这样,那么 TDelegate 参数可以消失)
  2. 如果没有,这样可以吗:

    // Signature of my extension method:
    public static bool HasAttribute<TDelagate>(this Expression<TDelagate> method, 
                                               Type attributeType)
    

问题是,您有一种类型要显式指定,而另一种类型又不想指定(这在 C# 中是不可能的)。

无论您是否可以执行其中任何一项,您仍然需要为表达式声明指定完整的委托类型(至少在 C# 3 中,不确定 C# 4 是否合法,我不知道知道是否有办法解决这个问题)。

如果 HasAttribute 以我认为的方式工作,我认为你可以做到 #1:

public static bool HasAttribute<TAttribute>(this LambdaExpression method) {
    if (method.Body.NodeType == ExpressionType.Call) {
        MethodCallExpression call = (MethodCallExpression)method.Body;
        return call.Method.GetCustomAttributes(typeof(TAttribute), true).Any();
    }
    return false;
}

编辑:

我认为你可以用这样的函数来简化第一行:

public static LambdaExpression GetMut<T>(Expression<Func<T>> f) { return f; }
public static LambdaExpression GetMut<T>(Expression<Action<T>> f) { return f; }

制作用法:

var l = new LogInModel(); // these variables can be inlined or declared 
var s = "";               // it makes no difference
var mut = Expressions.GetMut<AccountController>(c => c.LogIn(l,s));
mut.HasAttribute<ExportModelStateAttribute>().ShouldBeTrue();

用于玩弄这个的控制台应用程序的完整代码:

class Program {
    static void Main(string[] args) {
        var mut = Expressions.GetMut<AccountController>(c => c.LogIn(new LogInModel(), ""));
        mut.HasAttribute<ExportModelStateAttribute>().ShouldBeTrue();

        var failmut = Expressions.GetMut<AccountController>(c => c.LogInFails());
        failmut.HasAttribute<ExportModelStateAttribute>().ShouldBeTrue();

        Console.ReadKey();
    }
}

public class ExportModelStateAttribute : Attribute { }

public class ActionResult { }

public class LogInModel { }

public class AccountController {
    [ExportModelState]
    public ActionResult LogIn(LogInModel model, string s) {
        return new ActionResult();
    }

    public void LogInFails() {}
}

public static class Expressions {
    // only need this to find the method given the class T
    public static LambdaExpression GetMut<T>(Expression<Action<T>> func) { return func; }
    // Signature of my extension method:
    public static bool HasAttribute<TAttribute>(this LambdaExpression method) {
        if (method.Body.NodeType == ExpressionType.Call) {
            MethodCallExpression call = (MethodCallExpression)method.Body;
            return call.Method.GetCustomAttributes(typeof(TAttribute), true).Any();
        }
        return false;
    }

    public static void ShouldBeTrue(this bool obj) {
        Console.WriteLine(obj);
    }
}

【讨论】:

  • 这种方法看起来很有希望。如果我将方法表达式键入为LambdaExpression,使用行会怎样?
  • @your 编辑:但这是否还需要我实现一个新的GetMut-方法(Mut for member under test, btw...) 对于表达式中我需要的每个参数数量?也就是说,一个用于Func&lt;T&gt;,一个用于Func&lt;T1, T2&gt; 等等...?
  • 我不这么认为。我不确定它为什么有效,但显然它确实有效(对于任意数量的参数)。实际上,对于任何这些异常,您显然甚至不需要GetMut&lt;T&gt;(Expression&lt;Func&lt;T&gt;&gt; func)。编辑:嘿,它刚刚单击...这将创建一个动作,其第一个参数是T 类型,忽略返回的任何内容。现在更新答案...
猜你喜欢
  • 2017-07-26
  • 1970-01-01
  • 2012-11-12
  • 2014-01-07
  • 2019-06-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多