c# - 是否可以创建一个将方法组作为参数的通用方法
问题描述
我正在尝试创建一个允许传入“方法组”的方法。基本上这是为了测试我想确保调用方法的地方。FakeItEasy 已经允许以下代码
public static IAnyCallConfigurationWithReturnTypeSpecified<ReturnType> ReturnForMethod<SUT, ReturnType>(
this Fake<SUT> fake,
string methodName, ReturnType returnObj)
{
var call = fake.AnyCall().WithReturnType<ReturnType>().Where(s => s.Method.Name == methodName);
call.Returns(returnObj);
return call;
}
//Called using
new Fake<IDummyFactory>().ReturnForMethod(nameof(IDummyFactory.Create), testStr);
为了更简洁的代码,我更喜欢写类似的东西
public static IAnyCallConfigurationWithReturnTypeSpecified<ReturnType> ReturnForMethodF<SUT ,ReturnType>(
this Fake<SUT> fake,
MethodGroup method, ReturnType returnObj)
nameof 的工作方式基本相同。使用一个函数,所以我希望调用如下的方法
new Fake<IDummyFactory>().ReturnForMethod(s=> s.Create, testStr); --NoInvoke as just want method group
解决方案
“方法组”是一个只存在于语言语法中的概念,没有对应于方法组的运行时类型。
在我回答问题之前,您可以在 callsite 执行此操作:
new Fake<IDummyFactory>().ReturnForMethod(nameof(s.Create), testStr);
我认为没有任何理由s => s.Create
比nameof(s.Create)
.
现在是有趣的部分。方法组可转换为兼容的委托类型,由一组极其复杂的魔术规则管理。
因此,我们将寻找具有以下签名的方法:
public static IAnyCallConfigurationWithReturnTypeSpecified<TReturnType> ReturnForMethod<TSUT, TDelegate, TReturnType>(
this Fake<TSUT> fake,
Expression<Func<TSUT, TDelegate>> methodGroupExpression,
TReturnType returnObject) where TSUT : class
where TDelegate : Delegate
我们正在使用Expression
s,因此我们必须创建一个ExpressionVisitor
遍历Expression
树并查找适当类型的方法调用的方法。为简单起见,我们将假设您始终只使用一个x => x.Foo
表达式调用该方法。假设我们有一个执行此操作的访问者类,我们方法的主体很简单:
public static IAnyCallConfigurationWithReturnTypeSpecified<TReturnType> ReturnForMethod<TSUT, TDelegate, TReturnType>(
this Fake<TSUT> fake,
Expression<Func<TSUT, TDelegate>> methodGroupExpression,
TReturnType returnObject) where TSUT : class
where TDelegate : Delegate
{
var visitor = new ExtractMethodNameExpressionVisitor<TSUT>();
var methodName = visitor.ExtractMethodName(methodGroupExpression);
if (methodName is null)
{
throw new InvalidOperationException();
}
var call = fake.AnyCall().WithReturnType<TReturnType>().Where(s => s.Method.Name == methodName);
call.Returns(returnObject);
return call;
}
该表达式s => s.Create
在编译期间通过一些神奇的编译器生成的代码进行了丰富,这些代码将其转换为适当的委托类型。但是方法组本身作为ConstantExpression
type嵌套在其中的某个地方MethodInfo
。我们的访问者类将遍历树,找到这样的表达式并保存它的名称。
class ExtractMethodNameExpressionVisitor<T> : ExpressionVisitor
{
private string? _methodName;
public string? ExtractMethodName(Expression expression)
{
_methodName = null;
this.Visit(expression);
return _methodName;
}
protected override Expression VisitConstant(ConstantExpression node)
{
if (node.Type == typeof(MethodInfo) &&
node.Value is MethodInfo methodInfo &&
methodInfo.DeclaringType == typeof(T))
{
_methodName = methodInfo.Name;
}
return base.VisitConstant(node);
}
}
现在我们可以测试一下:
var fake = new Fake<IDummyFactory>();
fake.ReturnForMethod(s => s.Create, "testStr");
var result = fake.FakedObject.Create();
Console.WriteLine(result);
不幸的是,这不起作用。至少从 C#9 开始,委托类型在语言中是一种二等公民,对它们的泛型类型推断不起作用。所以上面的ReturnForMethod
调用不会编译,因为编译器无法推断出一个返回string
且不带参数的函数实际上是Func<string>
. 所以调用站点必须如下所示:
fake.ReturnForMethod<IDummyFactory, Func<string>, string>(s => s.Create, "testStr");
这使得该解决方案的吸引力大大降低。好消息是,如果我正确理解了这些建议,那么在 C#10(或者如果该功能错过 C#10 的发布窗口,则可能是 C#11)这将在没有显式类型签名的情况下工作。
您可以在此处找到完整的工作演示。
推荐阅读
- c# - 将 ReCaptcha 安装到 Umbraco V7
- amazon-web-services - AWS ECS:使用容量提供程序时如何在扩展策略中更改估计的 EC2 实例预热
- python - 编写一个检查单词是否仅由字母组成的函数
- r - 我的功能不起作用,但我输入的值确实
- python - Discord.py - 即使我定义了命令 ping,也没有定义它
- ios - iOS 中的 UITouch 时间戳有多准确?
- reactjs - 如何使用 flexDirection 列处理 flexbox 中的宽度?
- javascript - 具有相同名称但不同索引的 Jquery 循环复选框
- c# - LINQ 使用 where 子句在 c# 中循环遍历不同的数组
- reactjs - MobX ReactJS AntD 更新不会重新渲染