为什么我不能为 Moq 中的 List Find 方法创建回调?

Why can't I create a callback for the List Find method in Moq?

我创建了一个扩展方法,允许我将 List 视为 DbSet 以用于测试目的(实际上,我在此处关于堆栈溢出的另一个问题中发现了这个想法,它非常有用)。编码如下:

    public static DbSet<T> AsDbSet<T>(this List<T> sourceList) where T : class
    {
        var queryable = sourceList.AsQueryable();

        var mockDbSet = new Mock<DbSet<T>>();
        mockDbSet.As<IQueryable<T>>().Setup(m => m.Provider).Returns(queryable.Provider);
        mockDbSet.As<IQueryable<T>>().Setup(m => m.Expression).Returns(queryable.Expression);
        mockDbSet.As<IQueryable<T>>().Setup(m => m.ElementType).Returns(queryable.ElementType);
        mockDbSet.As<IQueryable<T>>().Setup(m => m.GetEnumerator()).Returns(queryable.GetEnumerator());
        mockDbSet.Setup(d => d.Add(It.IsAny<T>())).Callback<T>(sourceList.Add);
        mockDbSet.Setup(d => d.Find(It.IsAny<object[]>())).Callback(sourceList.Find);
        return mockDbSet.Object;
    }

我使用 Add 有一段时间了,效果很好。但是,当我尝试为 Find 添加回调时,出现编译器错误,指出它无法将方法组转换为操作。为什么sourceList.Add是一个Action,而sourceList.Find是一个方法组?

我承认我对 C# 委托不是特别熟悉,所以我很可能遗漏了一些非常明显的东西。提前致谢。

Add 起作用的原因是因为 List<T>.Add 方法组包含一个方法,该方法采用 T 类型的单个参数并且 return 为 void。此方法与 Action<T> 具有相同的签名,Action<T>Callback 方法的重载之一(具有单个泛型类型参数 Callback<T> 的方法),因此 List<T>.Add 方法组可以转换为 Action<T>.

使用 Find,您正在尝试调用 Callback 方法(与 Callback<T> 相反),该方法需要 Action 参数(与 Action<T>).这里的区别在于 Action 不接受任何参数,而 Action<T> 接受类型 T 的单个参数。List<T>.Find 方法组不能转换为 Action因为所有 Find 方法(反正只有一个)都接受输入参数。

以下将编译:

    public static DbSet<T> AsDbSet<T>(this List<T> sourceList) where T : class
    {
        var mockDbSet = new Mock<DbSet<T>>();
        mockDbSet.Setup(d => d.Find(It.IsAny<object[]>())).Callback<Predicate<T>>(t => sourceList.Find(t));
        return mockDbSet.Object;
    }

请注意,我调用了 .Callback<Predicate<T>>,因为 List<T>.Find 方法需要 Predicate 类型的参数。另请注意,我不得不写 t => sourceList.Find(t) 而不是 sourceList.Find,因为 Find return 是一个值(这意味着它与 Action<Predicate<T>> 的签名不匹配)。通过将其写成 lambda 表达式,return 值将被丢弃。

请注意,虽然此编译它实际上不会工作,因为 DbSet.Find 方法实际上采用 object[] 作为参数,而不是 Predicate<T>,因此您可能必须这样做像这样:

    public static DbSet<T> AsDbSet<T>(this List<T> sourceList) where T : class
    {
        var mockDbSet = new Mock<DbSet<T>>();
        mockDbSet.Setup(d => d.Find(It.IsAny<object[]>())).Callback<object[]>(keyValues => sourceList.Find(keyValues.Contains));
        return mockDbSet.Object;
    }

这最后一点与如何使用 Moq 库、如何使用方法组、委托和 lambda 有更多关系——这一行有各种各样的语法糖,隐藏了与以下内容实际相关的内容编译器,什么不是。