是否可以创建一个将方法组作为参数的通用方法
Is it possible to create a generic method that take a method group as an argument
我正在尝试创建一个允许传入“方法组”的方法。基本上这是为了测试我想确保调用了一个方法的位置。 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
“方法组”是一个只存在于语言语法中的概念,不存在与方法组相对应的运行时类型。
在我回答问题之前,你可以在调用站点这样做:
new Fake<IDummyFactory>().ReturnForMethod(nameof(s.Create), testStr);
我看不出有任何理由 s => s.Create
会比 nameof(s.Create)
更好或更具可读性。
现在是有趣的部分。方法组可转换为兼容的委托类型 as governed by an impossibly complicated set of magic rules.
所以我们要寻找一个签名为:
的方法
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
表达式调用该方法。假设我们有一个访问者 class 执行此操作,我们方法的主体很简单:
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
在编译期间用一些神奇的编译器生成的代码进行了丰富,这些代码将其转换为适当的委托类型。但是方法组本身作为 MethodInfo
类型的 ConstantExpression
嵌套在其中的某处。我们的访问者 class 将遍历树,找到这样的表达式并保存它的名称。
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 开始,委托类型是语言中的第二个 class 公民,对它们的泛型类型推断不起作用。所以上面的 ReturnForMethod
调用不会编译,因为编译器无法推断出返回 string
且不带参数的函数实际上是 Func<string>
。所以调用站点必须如下所示:
fake.ReturnForMethod<IDummyFactory, Func<string>, string>(s => s.Create, "testStr");
这使得这个解决方案的吸引力大打折扣。好消息是,如果我正确理解这些建议,在 C#10 中(或者可能是 C#11,如果该功能错过了 C#10 的发布 window),这将在没有显式类型签名的情况下工作。
您可以找到完整的工作演示 here。
我正在尝试创建一个允许传入“方法组”的方法。基本上这是为了测试我想确保调用了一个方法的位置。 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
“方法组”是一个只存在于语言语法中的概念,不存在与方法组相对应的运行时类型。
在我回答问题之前,你可以在调用站点这样做:
new Fake<IDummyFactory>().ReturnForMethod(nameof(s.Create), testStr);
我看不出有任何理由 s => s.Create
会比 nameof(s.Create)
更好或更具可读性。
现在是有趣的部分。方法组可转换为兼容的委托类型 as governed by an impossibly complicated set of magic rules.
所以我们要寻找一个签名为:
的方法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
表达式调用该方法。假设我们有一个访问者 class 执行此操作,我们方法的主体很简单:
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
在编译期间用一些神奇的编译器生成的代码进行了丰富,这些代码将其转换为适当的委托类型。但是方法组本身作为 MethodInfo
类型的 ConstantExpression
嵌套在其中的某处。我们的访问者 class 将遍历树,找到这样的表达式并保存它的名称。
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 开始,委托类型是语言中的第二个 class 公民,对它们的泛型类型推断不起作用。所以上面的 ReturnForMethod
调用不会编译,因为编译器无法推断出返回 string
且不带参数的函数实际上是 Func<string>
。所以调用站点必须如下所示:
fake.ReturnForMethod<IDummyFactory, Func<string>, string>(s => s.Create, "testStr");
这使得这个解决方案的吸引力大打折扣。好消息是,如果我正确理解这些建议,在 C#10 中(或者可能是 C#11,如果该功能错过了 C#10 的发布 window),这将在没有显式类型签名的情况下工作。
您可以找到完整的工作演示 here。