2

我正在研究一个 CRUD 测试课程,因为我厌倦了在验证我的 NHibernate 映射时重复相同的测试模式。

我已经重构了代码,并且已经达到了一切都按照我设想的方式工作的地步,但我感到很恼火。一切都基于字符串,反射方法使用这些字符串调用适当的存储库方法并获取相应属性的值,如实体 ID。

这行得通,但我确信我不需要为这些事情使用字符串。

所以我开始使用 Linq。我不是一个重度 Linq 用户,下面的代码让我完全困惑。

它工作得几乎完美(我会在一秒钟内完成),我很高兴它工作得很好,但我真的很想知道为什么。

[Test]
    public void TestCanUseTesterWithLinqSecondEffort()
    {
        IRepositoryTestContextManager contextManager = new NHRepositoryTestContextManager();
        contextManager.SetUpRepositoryContextAndSchema();
        TestInsertMethodWithLinq<NHClientRepository, Client>((x, y) => x.InsertClient(y), _newClient);
        contextManager.ResetRepositoryContext();
        Client retrievedClient = TestRetrieveMethodWithLinq<NHClientRepository, Client, string>((clientRepository, publicId) => clientRepository.GetClient(publicId), client => client.PublicId, _newClient);
        contextManager.TearDownRepositoryContext();
        Assert.IsNotNull(retrievedClient);
        Assert.AreNotSame(_newClient, retrievedClient);
        Assert.AreEqual(_newClient.Id, retrievedClient.Id);
        Assert.AreEqual(_newClient.PublicId, retrievedClient.PublicId);
        Assert.AreEqual(_newClient.Name, retrievedClient.Name);
    }

    private void TestInsertMethodWithLinq<TRepositoryType, TEntityType>(Action<TRepositoryType, TEntityType> insertMethod, TEntityType entity)
        where TRepositoryType : class, new()
    {            
        insertMethod.Invoke(new TRepositoryType(), entity);
    }

    private TEntityType TestRetrieveMethodWithLinq<TRepositoryType, TEntityType, TArgumentType>(Func<TRepositoryType, TArgumentType, TEntityType> retrieveMethod, Func<TEntityType, TArgumentType> paramValue, TEntityType theEntity)
        where TRepositoryType : class, new()
    {
        return retrieveMethod.Invoke(new TRepositoryType(), paramValue(theEntity));
    }

具体来说,我说的是两个调用的委托(我知道一个不必调用调用来使用委托。我将其包括在内以进行澄清)。编译器如何转换 Linq 表达式,以便在新实例化的类(在本例中为 TRepositoryTypes)上调用正确的方法?

关于“几乎完美”中的几乎,如果在调用方法的处理过程中出现异常,则将异常吞噬。也不知道为什么会这样,但我已经可以看到测试未完成并且由于异常被吞没而遗漏了问题的场景。

咀嚼那个。提前致谢。

4

3 回答 3

2

编译器如何转换 Linq 表达式,以便在新实例化的类(在本例中为 TRepositoryTypes)上调用正确的方法?

它们实际上不是 Linq 表达式 ( Expression<T>),只是常规的 ol' lambda。

编译器只是在“匿名”类中创建一个“匿名”方法来捕获任何变量。在这种情况下,您没有捕获的变量 - 所以:

 TestInsertMethodWithLinq<NHClientRepository, Client>(
    (x, y) => x.InsertClient(y), _newClient
 );

简单地转换为“匿名”方法:

class __abcde {
    public void __fghij(NHClientRepository x, Client y) {
       x.InsertClient(y);
    }
 }

调用者被转换为:

 var c = new __abcde();
 c.__fghij(new NHClientRepository(), _newClient);

由于您的通用约束需要一个new()无参数构造函数,因此编译器能够插入该new NHClientRepository()位。

于 2009-08-12T02:35:28.090 回答
0

我不是 NHibernate 用户(或任何其他 ORM),所以我只能推测。但我怀疑这里发生的事情是你正在使用闭包。当您创建一个闭包时,您在 lambda 表达式中使用的变量与方法一起被捕获/封闭/提升到一个类中,因此即使您在很久以后才调用该方法,该值也保持最新。

于 2009-08-12T01:44:22.360 回答
0

我可能错过了一些东西,但我相信你指的是泛型和多态性。

您希望传入一个 TRepository 类型,并调用所有 TRepository 类型都可以调用的方法。魔法发生在这里:

TestInsertMethodWithLinq<NHClientRepository, Client>((x, y) => x.InsertClient(y), _newClient);        

您说我将调用此方法并使用类型 NHClientRepository 和 Client。然后进行函数调用,通用方法将在这一行中使用 NHClient:

insertMethod.Invoke(new TRepositoryType(), entity);

这会将 TRepositoryType() 解析为 NHClientRepository。然后使用您传入的类型和操作调用该方法,为您提供结果。

如果您想尝试跟踪正在发生的事情,您可能希望在测试中设置一个断点并在观察调用堆栈的同时逐步使用调试器,您可以查看所有泛型类型和方法调用等以帮助您了解发生了什么在。

编辑:参考您的评论,C# 3 类型推断正在帮助您完成操作。类型推断是将 x 和 y 映射为 NHClientRepository 和 Client,然后允许调用工作。由于编译器在编译时知道您正在执行什么,因此它能够帮助您解决这个问题。如果您在不调用的情况下调用 the,您应该能够看到智能感知能够帮助您insertMethod

我假设这是你的最后一块拼图?

于 2009-08-12T02:08:52.953 回答