我写了有趣的Linq代码,但我不知道它是如何工作的或原因

时间:2009-08-12 01:37:37

标签: c# generics delegates invoke

我正在研究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));
    }

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

关于几乎完全“几乎完美”,如果在处理调用方法期间存在异常,则吞下异常。不知道为什么会这样,但是我已经可以看到测试未完成的情况,并且由于吞下了异常而错过了问题。

嚼开它。提前谢谢。

3 个答案:

答案 0 :(得分:2)

  

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

他们实际上不是Linq表达式(Expression<T>),只是常规的'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() no-args构造函数,因此编译器能够插入new NHClientRepository()位。

答案 1 :(得分:0)

我不是NHibernate用户(或任何其他ORM),所以我只能推测。但我怀疑这里发生的是你正在使用一个闭包。当您创建闭包时,您在lambda表达式中使用的变量将被捕获/关闭/提升到类中以及该方法,以便即使您不调用该值也保持当前状态直到很久以后的方法。

答案 2 :(得分: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,然后允许调用工作。由于编译器在编译时知道您正在执行的操作,因此能够为您提供帮助。如果您在不调用的情况下调用insertMethod,您应该能够看到intellisense能够帮助您。

我认为那是你最后一块拼图?