方法组的行为与 lambda 不同?
Method group behaving differently than lambda?
我正在使用 Moq
模拟一些界面。这是:
var titleGenerator = new Mock<ITitleGenerator>();
titleGenerator.Setup(t => t.GenerateTitle()).Returns(Guid.NewGuid().ToString);
Console.WriteLine(titleGenerator.Object.GenerateTitle());
Console.WriteLine(titleGenerator.Object.GenerateTitle());
它打印了两次相同的值。但是如果我用这个替换第二行:
titleGenerator.Setup(t => t.GenerateTitle()).Returns(() => Guid.NewGuid().ToString());
它 returns 每次调用的唯一值。
我一直认为方法组只是lambda表达式的捷径。有什么不同吗?我试着在文档中搜索任何解释。有人可以启发我吗?
看起来方法组对表达式求值一次并以某种方式缓存它?还是跟Moq
有什么关系?
在您的第一个示例中,您将传递单个 Guid
的 ToString
函数,然后在每次调用时调用该函数。相当于:
Guid guid = Guid.NewGuid();
titleGenerator.Setup(t => t.GenerateTitle()).Returns(guid.ToString)
在您的第二个示例中,您传递的函数首先创建一个新的 Guid
,然后对其调用 ToString()
。
区别在于输入。在第一种情况下,"method group" 实际上是 Guid.ToString
的委托。但由于它需要一个 实例 作为 "input",该实例是委托表达式的一部分,因此每次都使用相同的 "input"。
相当于:
var titleGenerator = new Mock<ITitleGenerator>();
Guid g = Guid.NewGuid();
titleGenerator.Setup(t => t.GenerateTitle()).Returns(g.ToString);
在第二种情况下,代表没有输入。 Guid
实例在委托 中计算,因此每次都使用一个新的Guid
。
对于可能更容易理解的等效示例,代码:
var id = 1;
Func<string> f = id.ToString;
id = 2;
Console.WriteLine(f()); // 1
会写成"1"
,而:
var id = 1;
Func<string> f = () => id.ToString();
id = 2;
Console.WriteLine(f()); // 2
会写"2"
.
在 第一种情况 中,委托(Func<>
实例)f
的创建值为 1
作为 Target
and the method info for string int.ToString()
as Method
.稍后重新分配给 id
不会影响 f
.
在第二种情况中,事情会更加间接。编译器将生成一个对应于 =>
箭头的新方法。局部变量id
被captured或closed over(在closure的拉姆达)。这意味着,在幕后,id
确实在某处提升为 field
(编译器的选择)。当您的方法提到 id
时,它确实访问了该字段。与 =>
箭头对应的编译器生成的方法也读取该字段。现在 Func<>
被创建,其 Method
属性 是编译器生成的方法。由于这一切,这里的结果将是 "2"
。这就是 C# 中 匿名函数 的闭包语义。
您原来的最小起订量示例是一样的。有问题的 Returns
重载采用参数 Func<TResult> valueFunction
,其中 TResult
在您的使用中是 string
。 valueFunction
就是我在更简单的示例中所说的 f
。
我正在使用 Moq
模拟一些界面。这是:
var titleGenerator = new Mock<ITitleGenerator>();
titleGenerator.Setup(t => t.GenerateTitle()).Returns(Guid.NewGuid().ToString);
Console.WriteLine(titleGenerator.Object.GenerateTitle());
Console.WriteLine(titleGenerator.Object.GenerateTitle());
它打印了两次相同的值。但是如果我用这个替换第二行:
titleGenerator.Setup(t => t.GenerateTitle()).Returns(() => Guid.NewGuid().ToString());
它 returns 每次调用的唯一值。
我一直认为方法组只是lambda表达式的捷径。有什么不同吗?我试着在文档中搜索任何解释。有人可以启发我吗?
看起来方法组对表达式求值一次并以某种方式缓存它?还是跟Moq
有什么关系?
在您的第一个示例中,您将传递单个 Guid
的 ToString
函数,然后在每次调用时调用该函数。相当于:
Guid guid = Guid.NewGuid();
titleGenerator.Setup(t => t.GenerateTitle()).Returns(guid.ToString)
在您的第二个示例中,您传递的函数首先创建一个新的 Guid
,然后对其调用 ToString()
。
区别在于输入。在第一种情况下,"method group" 实际上是 Guid.ToString
的委托。但由于它需要一个 实例 作为 "input",该实例是委托表达式的一部分,因此每次都使用相同的 "input"。
相当于:
var titleGenerator = new Mock<ITitleGenerator>();
Guid g = Guid.NewGuid();
titleGenerator.Setup(t => t.GenerateTitle()).Returns(g.ToString);
在第二种情况下,代表没有输入。 Guid
实例在委托 中计算,因此每次都使用一个新的Guid
。
对于可能更容易理解的等效示例,代码:
var id = 1;
Func<string> f = id.ToString;
id = 2;
Console.WriteLine(f()); // 1
会写成"1"
,而:
var id = 1;
Func<string> f = () => id.ToString();
id = 2;
Console.WriteLine(f()); // 2
会写"2"
.
在 第一种情况 中,委托(Func<>
实例)f
的创建值为 1
作为 Target
and the method info for string int.ToString()
as Method
.稍后重新分配给 id
不会影响 f
.
在第二种情况中,事情会更加间接。编译器将生成一个对应于 =>
箭头的新方法。局部变量id
被captured或closed over(在closure的拉姆达)。这意味着,在幕后,id
确实在某处提升为 field
(编译器的选择)。当您的方法提到 id
时,它确实访问了该字段。与 =>
箭头对应的编译器生成的方法也读取该字段。现在 Func<>
被创建,其 Method
属性 是编译器生成的方法。由于这一切,这里的结果将是 "2"
。这就是 C# 中 匿名函数 的闭包语义。
您原来的最小起订量示例是一样的。有问题的 Returns
重载采用参数 Func<TResult> valueFunction
,其中 TResult
在您的使用中是 string
。 valueFunction
就是我在更简单的示例中所说的 f
。